// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/frame_host/render_frame_host_manager.h"

#include <stdint.h>

#include <tuple>
#include <utility>

#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/histogram_tester.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "content/browser/frame_host/navigation_controller_impl.h"
#include "content/browser/frame_host/navigation_entry_impl.h"
#include "content/browser/frame_host/navigation_request.h"
#include "content/browser/frame_host/navigator.h"
#include "content/browser/frame_host/render_frame_proxy_host.h"
#include "content/browser/site_instance_impl.h"
#include "content/browser/webui/web_ui_controller_factory_registry.h"
#include "content/common/frame_messages.h"
#include "content/common/frame_owner_properties.h"
#include "content/common/input_messages.h"
#include "content/common/site_isolation_policy.h"
#include "content/common/view_messages.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_source.h"
#include "content/public/browser/notification_types.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_widget_host_iterator.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/browser/web_ui_controller.h"
#include "content/public/common/bindings_policy.h"
#include "content/public/common/browser_side_navigation_policy.h"
#include "content/public/common/javascript_message_type.h"
#include "content/public/common/previews_state.h"
#include "content/public/common/url_constants.h"
#include "content/public/common/url_utils.h"
#include "content/public/test/browser_side_navigation_test_utils.h"
#include "content/public/test/mock_render_process_host.h"
#include "content/public/test/test_notification_tracker.h"
#include "content/public/test/test_utils.h"
#include "content/test/test_content_browser_client.h"
#include "content/test/test_content_client.h"
#include "content/test/test_render_frame_host.h"
#include "content/test/test_render_view_host.h"
#include "content/test/test_web_contents.h"
#include "net/base/load_flags.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/WebKit/public/platform/WebInsecureRequestPolicy.h"
#include "third_party/WebKit/public/web/WebSandboxFlags.h"
#include "ui/base/page_transition_types.h"

namespace content {
namespace {

    // Helper to check that the provided RenderProcessHost received exactly one
    // page focus message with the provided focus and routing ID values.
    void VerifyPageFocusMessage(MockRenderProcessHost* rph,
        bool expected_focus,
        int expected_routing_id)
    {
        const IPC::Message* message = rph->sink().GetUniqueMessageMatching(InputMsg_SetFocus::ID);
        EXPECT_TRUE(message);
        EXPECT_EQ(expected_routing_id, message->routing_id());
        InputMsg_SetFocus::Param params;
        EXPECT_TRUE(InputMsg_SetFocus::Read(message, &params));
        EXPECT_EQ(expected_focus, std::get<0>(params));
    }

    // Helper function for strict mixed content checking tests.
    void CheckInsecureRequestPolicyIPC(
        TestRenderFrameHost* rfh,
        blink::WebInsecureRequestPolicy expected_param,
        int expected_routing_id)
    {
        const IPC::Message* message = rfh->GetProcess()->sink().GetUniqueMessageMatching(
            FrameMsg_EnforceInsecureRequestPolicy::ID);
        ASSERT_TRUE(message);
        EXPECT_EQ(expected_routing_id, message->routing_id());
        FrameMsg_EnforceInsecureRequestPolicy::Param params;
        EXPECT_TRUE(FrameMsg_EnforceInsecureRequestPolicy::Read(message, &params));
        EXPECT_EQ(expected_param, std::get<0>(params));
    }

    class RenderFrameHostManagerTestWebUIControllerFactory
        : public WebUIControllerFactory {
    public:
        RenderFrameHostManagerTestWebUIControllerFactory()
            : should_create_webui_(false)
            , type_(1)
        {
            CHECK_NE(reinterpret_cast<WebUI::TypeID>(type_), WebUI::kNoWebUI);
        }
        ~RenderFrameHostManagerTestWebUIControllerFactory() override { }

        void set_should_create_webui(bool should_create_webui)
        {
            should_create_webui_ = should_create_webui;
        }

        // This method simulates the expectation that different WebUI instance types
        // would be created. The |type| value will be returned by GetWebUIType casted
        // to WebUI::TypeID.
        // As WebUI::TypeID is a typedef to void pointer, factory implementations
        // return values that they know to be unique to their respective cases. So
        // values set here should be safe if kept very low (just above zero).
        void set_webui_type(uintptr_t type)
        {
            CHECK_NE(reinterpret_cast<WebUI::TypeID>(type), WebUI::kNoWebUI);
            type_ = type;
        }

        // WebUIFactory implementation.
        WebUIController* CreateWebUIControllerForURL(WebUI* web_ui,
            const GURL& url) const override
        {
            // If WebUI creation is enabled for the test and this is a WebUI URL,
            // returns a new instance.
            if (should_create_webui_ && HasWebUIScheme(url))
                return new WebUIController(web_ui);
            return nullptr;
        }

        WebUI::TypeID GetWebUIType(BrowserContext* browser_context,
            const GURL& url) const override
        {
            // If WebUI creation is enabled for the test and this is a WebUI URL,
            // returns a mock WebUI type.
            if (should_create_webui_ && HasWebUIScheme(url)) {
                return reinterpret_cast<WebUI::TypeID>(type_);
            }
            return WebUI::kNoWebUI;
        }

        bool UseWebUIForURL(BrowserContext* browser_context,
            const GURL& url) const override
        {
            return HasWebUIScheme(url);
        }

        bool UseWebUIBindingsForURL(BrowserContext* browser_context,
            const GURL& url) const override
        {
            return HasWebUIScheme(url);
        }

    private:
        bool should_create_webui_;
        uintptr_t type_;

        DISALLOW_COPY_AND_ASSIGN(RenderFrameHostManagerTestWebUIControllerFactory);
    };

    class BeforeUnloadFiredWebContentsDelegate : public WebContentsDelegate {
    public:
        BeforeUnloadFiredWebContentsDelegate() { }
        ~BeforeUnloadFiredWebContentsDelegate() override { }

        void BeforeUnloadFired(WebContents* web_contents,
            bool proceed,
            bool* proceed_to_fire_unload) override
        {
            *proceed_to_fire_unload = proceed;
        }

    private:
        DISALLOW_COPY_AND_ASSIGN(BeforeUnloadFiredWebContentsDelegate);
    };

    class CloseWebContentsDelegate : public WebContentsDelegate {
    public:
        CloseWebContentsDelegate()
            : close_called_(false)
        {
        }
        ~CloseWebContentsDelegate() override { }

        void CloseContents(WebContents* web_contents) override
        {
            close_called_ = true;
        }

        bool is_closed() { return close_called_; }

    private:
        DISALLOW_COPY_AND_ASSIGN(CloseWebContentsDelegate);

        bool close_called_;
    };

    // This observer keeps track of the last deleted RenderViewHost to avoid
    // accessing it and causing use-after-free condition.
    class RenderViewHostDeletedObserver : public WebContentsObserver {
    public:
        RenderViewHostDeletedObserver(RenderViewHost* rvh)
            : WebContentsObserver(WebContents::FromRenderViewHost(rvh))
            , process_id_(rvh->GetProcess()->GetID())
            , routing_id_(rvh->GetRoutingID())
            , deleted_(false)
        {
        }

        void RenderViewDeleted(RenderViewHost* render_view_host) override
        {
            if (render_view_host->GetProcess()->GetID() == process_id_ && render_view_host->GetRoutingID() == routing_id_) {
                deleted_ = true;
            }
        }

        bool deleted()
        {
            return deleted_;
        }

    private:
        int process_id_;
        int routing_id_;
        bool deleted_;

        DISALLOW_COPY_AND_ASSIGN(RenderViewHostDeletedObserver);
    };

    // This observer keeps track of the last created RenderFrameHost to allow tests
    // to ensure that no RenderFrameHost objects are created when not expected.
    class RenderFrameHostCreatedObserver : public WebContentsObserver {
    public:
        RenderFrameHostCreatedObserver(WebContents* web_contents)
            : WebContentsObserver(web_contents)
            , created_(false)
        {
        }

        void RenderFrameCreated(RenderFrameHost* render_frame_host) override
        {
            created_ = true;
        }

        bool created()
        {
            return created_;
        }

    private:
        bool created_;

        DISALLOW_COPY_AND_ASSIGN(RenderFrameHostCreatedObserver);
    };

    // This WebContents observer keep track of its RVH change.
    class RenderViewHostChangedObserver : public WebContentsObserver {
    public:
        RenderViewHostChangedObserver(WebContents* web_contents)
            : WebContentsObserver(web_contents)
            , host_changed_(false)
        {
        }

        // WebContentsObserver.
        void RenderViewHostChanged(RenderViewHost* old_host,
            RenderViewHost* new_host) override
        {
            host_changed_ = true;
        }

        bool DidHostChange()
        {
            bool host_changed = host_changed_;
            Reset();
            return host_changed;
        }

        void Reset() { host_changed_ = false; }

    private:
        bool host_changed_;
        DISALLOW_COPY_AND_ASSIGN(RenderViewHostChangedObserver);
    };

    // This observer is used to check whether IPC messages are being filtered for
    // swapped out RenderFrameHost objects. It observes the plugin crash and favicon
    // update events, which the FilterMessagesWhileSwappedOut test simulates being
    // sent. The test is successful if the event is not observed.
    // See http://crbug.com/351815
    class PluginFaviconMessageObserver : public WebContentsObserver {
    public:
        PluginFaviconMessageObserver(WebContents* web_contents)
            : WebContentsObserver(web_contents)
            , plugin_crashed_(false)
            , favicon_received_(false)
        {
        }

        void PluginCrashed(const base::FilePath& plugin_path,
            base::ProcessId plugin_pid) override
        {
            plugin_crashed_ = true;
        }

        void DidUpdateFaviconURL(const std::vector<FaviconURL>& candidates) override
        {
            favicon_received_ = true;
        }

        bool plugin_crashed()
        {
            return plugin_crashed_;
        }

        bool favicon_received()
        {
            return favicon_received_;
        }

    private:
        bool plugin_crashed_;
        bool favicon_received_;

        DISALLOW_COPY_AND_ASSIGN(PluginFaviconMessageObserver);
    };

} // namespace

class RenderFrameHostManagerTest : public RenderViewHostImplTestHarness {
public:
    void SetUp() override
    {
        RenderViewHostImplTestHarness::SetUp();
        WebUIControllerFactory::RegisterFactory(&factory_);
    }

    void TearDown() override
    {
        RenderViewHostImplTestHarness::TearDown();
        WebUIControllerFactory::UnregisterFactoryForTesting(&factory_);
    }

    void set_should_create_webui(bool should_create_webui)
    {
        factory_.set_should_create_webui(should_create_webui);
    }

    void set_webui_type(int type) { factory_.set_webui_type(type); }

    void NavigateActiveAndCommit(const GURL& url)
    {
        // Note: we navigate the active RenderFrameHost because previous navigations
        // won't have committed yet, so NavigateAndCommit does the wrong thing
        // for us.
        controller().LoadURL(
            url, Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
        int entry_id = controller().GetPendingEntry()->GetUniqueID();

        // Simulate the BeforeUnload_ACK that is received from the current renderer
        // for a cross-site navigation.
        // PlzNavigate: it is necessary to call PrepareForCommit before getting the
        // main and the pending frame because when we are trying to navigate to a
        // WebUI from a new tab, a RenderFrameHost is created to display it that is
        // committed immediately (since it is a new tab). Therefore the main frame
        // is replaced without a pending frame being created, and we don't get the
        // right values for the RFH to navigate: we try to use the old one that has
        // been deleted in the meantime.
        contents()->GetMainFrame()->PrepareForCommit();

        TestRenderFrameHost* old_rfh = contents()->GetMainFrame();
        TestRenderFrameHost* active_rfh = contents()->GetPendingMainFrame()
            ? contents()->GetPendingMainFrame()
            : old_rfh;
        EXPECT_TRUE(old_rfh->is_active());

        // Use an observer to avoid accessing a deleted renderer later on when the
        // state is being checked.
        RenderFrameDeletedObserver rfh_observer(old_rfh);
        RenderViewHostDeletedObserver rvh_observer(old_rfh->GetRenderViewHost());
        active_rfh->SendNavigate(entry_id, true, url);

        // Make sure that we start to run the unload handler at the time of commit.
        if (old_rfh != active_rfh && !rfh_observer.deleted()) {
            EXPECT_FALSE(old_rfh->is_active());
        }

        // Simulate the swap out ACK coming from the pending renderer.  This should
        // either shut down the old RFH or leave it in a swapped out state.
        if (old_rfh != active_rfh) {
            old_rfh->OnSwappedOut();
            EXPECT_TRUE(rfh_observer.deleted());
        }
        EXPECT_EQ(active_rfh, contents()->GetMainFrame());
        EXPECT_EQ(NULL, contents()->GetPendingMainFrame());
    }

    bool ShouldSwapProcesses(RenderFrameHostManager* manager,
        const NavigationEntryImpl* current_entry,
        const NavigationEntryImpl* new_entry) const
    {
        CHECK(new_entry);
        BrowserContext* browser_context = manager->delegate_->GetControllerForRenderManager().GetBrowserContext();
        const GURL& current_effective_url = current_entry ? SiteInstanceImpl::GetEffectiveURL(browser_context,
                                                current_entry->GetURL())
                                                          : manager->render_frame_host_->GetSiteInstance()->GetSiteURL();
        bool current_is_view_source_mode = current_entry ? current_entry->IsViewSourceMode() : new_entry->IsViewSourceMode();
        return manager->ShouldSwapBrowsingInstancesForNavigation(
            current_effective_url,
            current_is_view_source_mode,
            new_entry->site_instance(),
            SiteInstanceImpl::GetEffectiveURL(browser_context, new_entry->GetURL()),
            new_entry->IsViewSourceMode());
    }

    // Creates a test RenderViewHost that's swapped out.
    void CreateSwappedOutRenderViewHost()
    {
        const GURL kChromeURL("chrome://foo");
        const GURL kDestUrl("http://www.google.com/");

        // Navigate our first tab to a chrome url and then to the destination.
        NavigateActiveAndCommit(kChromeURL);
        TestRenderFrameHost* ntp_rfh = contents()->GetMainFrame();

        // Navigate to a cross-site URL.
        contents()->GetController().LoadURL(
            kDestUrl, Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
        int entry_id = contents()->GetController().GetPendingEntry()->GetUniqueID();
        contents()->GetMainFrame()->PrepareForCommit();
        EXPECT_TRUE(contents()->CrossProcessNavigationPending());

        // Manually increase the number of active frames in the
        // SiteInstance that ntp_rfh belongs to, to prevent it from being
        // destroyed when it gets swapped out.
        ntp_rfh->GetSiteInstance()->IncrementActiveFrameCount();

        TestRenderFrameHost* dest_rfh = contents()->GetPendingMainFrame();
        CHECK(dest_rfh);
        EXPECT_NE(ntp_rfh, dest_rfh);

        // BeforeUnload finishes.
        ntp_rfh->SendBeforeUnloadACK(true);

        dest_rfh->SendNavigate(entry_id, true, kDestUrl);
        ntp_rfh->OnSwappedOut();
    }

    // Returns the RenderFrameHost that should be used in the navigation to
    // |entry|.
    RenderFrameHostImpl* NavigateToEntry(
        RenderFrameHostManager* manager,
        const NavigationEntryImpl& entry)
    {
        // Tests currently only navigate using main frame FrameNavigationEntries.
        FrameNavigationEntry* frame_entry = entry.root_node()->frame_entry.get();
        if (IsBrowserSideNavigationEnabled()) {
            NavigationControllerImpl* controller = static_cast<NavigationControllerImpl*>(manager->current_frame_host()
                                                                                              ->frame_tree_node()
                                                                                              ->navigator()
                                                                                              ->GetController());
            FrameMsg_Navigate_Type::Value navigate_type = entry.restore_type() == RestoreType::NONE
                ? FrameMsg_Navigate_Type::NORMAL
                : FrameMsg_Navigate_Type::RESTORE;
            std::unique_ptr<NavigationRequest> navigation_request = NavigationRequest::CreateBrowserInitiated(
                manager->frame_tree_node_, frame_entry->url(),
                frame_entry->referrer(), *frame_entry, entry, navigate_type,
                PREVIEWS_UNSPECIFIED, false, false, base::TimeTicks::Now(),
                controller);

            // Simulates request creation that triggers the 1st internal call to
            // GetFrameHostForNavigation.
            manager->DidCreateNavigationRequest(navigation_request.get());

            // And also simulates the 2nd and final call to GetFrameHostForNavigation
            // that determines the final frame that will commit the navigation.
            TestRenderFrameHost* frame_host = static_cast<TestRenderFrameHost*>(
                manager->GetFrameHostForNavigation(*navigation_request));
            CHECK(frame_host);
            frame_host->set_pending_commit(true);
            return frame_host;
        }

        return manager->Navigate(frame_entry->url(), *frame_entry, entry, false);
    }

    // Returns the pending RenderFrameHost.
    // PlzNavigate: returns the speculative RenderFrameHost.
    RenderFrameHostImpl* GetPendingFrameHost(
        RenderFrameHostManager* manager)
    {
        if (IsBrowserSideNavigationEnabled())
            return manager->speculative_render_frame_host_.get();

        return manager->pending_frame_host();
    }

    // Exposes RenderFrameHostManager::CollectOpenerFrameTrees for testing.
    void CollectOpenerFrameTrees(
        FrameTreeNode* node,
        std::vector<FrameTree*>* opener_frame_trees,
        base::hash_set<FrameTreeNode*>* nodes_with_back_links)
    {
        node->render_manager()->CollectOpenerFrameTrees(opener_frame_trees,
            nodes_with_back_links);
    }

    void BaseSimultaneousNavigationWithOneWebUI(
        const std::function<void(RenderFrameHostImpl*,
            RenderFrameHostImpl*,
            WebUIImpl*,
            RenderFrameHostManager*)>& commit_lambda);

    void BaseSimultaneousNavigationWithTwoWebUIs(
        const std::function<void(RenderFrameHostImpl*,
            RenderFrameHostImpl*,
            WebUIImpl*,
            WebUIImpl*,
            RenderFrameHostManager*)>& commit_lambda);

private:
    RenderFrameHostManagerTestWebUIControllerFactory factory_;
};

// Tests that when you navigate from a chrome:// url to another page, and
// then do that same thing in another tab, that the two resulting pages have
// different SiteInstances, BrowsingInstances, and RenderProcessHosts. This is
// a regression test for bug 9364.
TEST_F(RenderFrameHostManagerTest, NewTabPageProcesses)
{
    set_should_create_webui(true);
    const GURL kChromeUrl("chrome://foo");
    const GURL kDestUrl("http://www.google.com/");

    // Navigate our first tab to the chrome url and then to the destination,
    // ensuring we grant bindings to the chrome URL.
    NavigateActiveAndCommit(kChromeUrl);
    EXPECT_TRUE(active_rvh()->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
    NavigateActiveAndCommit(kDestUrl);

    EXPECT_FALSE(contents()->GetPendingMainFrame());

    // Make a second tab.
    std::unique_ptr<TestWebContents> contents2(
        TestWebContents::Create(browser_context(), NULL));

    // Load the two URLs in the second tab. Note that the first navigation creates
    // a RFH that's not pending (since there is no cross-site transition), so
    // we use the committed one.
    contents2->GetController().LoadURL(
        kChromeUrl, Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
    int entry_id = contents2->GetController().GetPendingEntry()->GetUniqueID();
    contents2->GetMainFrame()->PrepareForCommit();
    TestRenderFrameHost* ntp_rfh2 = contents2->GetMainFrame();
    EXPECT_FALSE(contents2->CrossProcessNavigationPending());
    ntp_rfh2->SendNavigate(entry_id, true, kChromeUrl);

    // The second one is the opposite, creating a cross-site transition and
    // requiring a beforeunload ack.
    contents2->GetController().LoadURL(
        kDestUrl, Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
    entry_id = contents2->GetController().GetPendingEntry()->GetUniqueID();
    contents2->GetMainFrame()->PrepareForCommit();
    EXPECT_TRUE(contents2->CrossProcessNavigationPending());
    TestRenderFrameHost* dest_rfh2 = contents2->GetPendingMainFrame();
    ASSERT_TRUE(dest_rfh2);

    dest_rfh2->SendNavigate(entry_id, true, kDestUrl);

    // The two RFH's should be different in every way.
    EXPECT_NE(contents()->GetMainFrame()->GetProcess(), dest_rfh2->GetProcess());
    EXPECT_NE(contents()->GetMainFrame()->GetSiteInstance(),
        dest_rfh2->GetSiteInstance());
    EXPECT_FALSE(dest_rfh2->GetSiteInstance()->IsRelatedSiteInstance(
        contents()->GetMainFrame()->GetSiteInstance()));

    // Navigate both to the new tab page, and verify that they share a
    // RenderProcessHost (not a SiteInstance).
    NavigateActiveAndCommit(kChromeUrl);
    EXPECT_FALSE(contents()->GetPendingMainFrame());

    contents2->GetController().LoadURL(
        kChromeUrl, Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
    entry_id = contents2->GetController().GetPendingEntry()->GetUniqueID();
    contents2->GetMainFrame()->PrepareForCommit();
    contents2->GetPendingMainFrame()->SendNavigate(entry_id, true, kChromeUrl);

    EXPECT_NE(contents()->GetMainFrame()->GetSiteInstance(),
        contents2->GetMainFrame()->GetSiteInstance());
    EXPECT_EQ(contents()->GetMainFrame()->GetSiteInstance()->GetProcess(),
        contents2->GetMainFrame()->GetSiteInstance()->GetProcess());
}

// Ensure that the browser ignores most IPC messages that arrive from a
// RenderViewHost that has been swapped out.  We do not want to take
// action on requests from a non-active renderer.  The main exception is
// for synchronous messages, which cannot be ignored without leaving the
// renderer in a stuck state.  See http://crbug.com/93427.
TEST_F(RenderFrameHostManagerTest, FilterMessagesWhileSwappedOut)
{
    const GURL kChromeURL("chrome://foo");
    const GURL kDestUrl("http://www.google.com/");
    std::vector<FaviconURL> icons;

    // Navigate our first tab to a chrome url and then to the destination.
    NavigateActiveAndCommit(kChromeURL);
    TestRenderFrameHost* ntp_rfh = contents()->GetMainFrame();
    TestRenderViewHost* ntp_rvh = ntp_rfh->GetRenderViewHost();

    // Send an update favicon message and make sure it works.
    {
        PluginFaviconMessageObserver observer(contents());
        EXPECT_TRUE(ntp_rfh->GetRenderViewHost()->GetWidget()->OnMessageReceived(
            ViewHostMsg_UpdateFaviconURL(
                ntp_rfh->GetRenderViewHost()->GetRoutingID(), icons)));
        EXPECT_TRUE(observer.favicon_received());
    }
    // Create one more frame in the same SiteInstance where ntp_rfh
    // exists so that it doesn't get deleted on navigation to another
    // site.
    ntp_rfh->GetSiteInstance()->IncrementActiveFrameCount();

    // Navigate to a cross-site URL.
    NavigateActiveAndCommit(kDestUrl);
    TestRenderFrameHost* dest_rfh = contents()->GetMainFrame();
    ASSERT_TRUE(dest_rfh);
    EXPECT_NE(ntp_rfh, dest_rfh);

    // The new RVH should be able to update its favicon.
    {
        PluginFaviconMessageObserver observer(contents());
        EXPECT_TRUE(dest_rfh->GetRenderViewHost()->GetWidget()->OnMessageReceived(
            ViewHostMsg_UpdateFaviconURL(
                dest_rfh->GetRenderViewHost()->GetRoutingID(), icons)));
        EXPECT_TRUE(observer.favicon_received());
    }

    // The old renderer, being slow, now updates the favicon. It should be
    // filtered out and not take effect.
    {
        PluginFaviconMessageObserver observer(contents());
        EXPECT_TRUE(
            ntp_rvh->GetWidget()->OnMessageReceived(ViewHostMsg_UpdateFaviconURL(
                dest_rfh->GetRenderViewHost()->GetRoutingID(), icons)));
        EXPECT_FALSE(observer.favicon_received());
    }
}

// Test that the ViewHostMsg_UpdateFaviconURL IPC message is ignored if the
// renderer is in the STATE_PENDING_SWAP_OUT_STATE. The favicon code assumes
// that it only gets ViewHostMsg_UpdateFaviconURL messages for the most recently
// committed navigation for each WebContentsImpl.
TEST_F(RenderFrameHostManagerTest, UpdateFaviconURLWhilePendingSwapOut)
{
    const GURL kChromeURL("chrome://foo");
    const GURL kDestUrl("http://www.google.com/");
    std::vector<FaviconURL> icons;

    // Navigate our first tab to a chrome url and then to the destination.
    NavigateActiveAndCommit(kChromeURL);
    TestRenderFrameHost* rfh1 = contents()->GetMainFrame();

    // Send an update favicon message and make sure it works.
    {
        PluginFaviconMessageObserver observer(contents());
        EXPECT_TRUE(rfh1->GetRenderViewHost()->GetWidget()->OnMessageReceived(
            ViewHostMsg_UpdateFaviconURL(rfh1->GetRenderViewHost()->GetRoutingID(),
                icons)));
        EXPECT_TRUE(observer.favicon_received());
    }

    // Create one more frame in the same SiteInstance where |rfh1| exists so that
    // it doesn't get deleted on navigation to another site.
    rfh1->GetSiteInstance()->IncrementActiveFrameCount();

    // Navigate to a cross-site URL and commit the new page.
    controller().LoadURL(
        kDestUrl, Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
    int entry_id = controller().GetPendingEntry()->GetUniqueID();
    contents()->GetMainFrame()->PrepareForCommit();
    TestRenderFrameHost* rfh2 = contents()->GetPendingMainFrame();
    contents()->TestDidNavigate(rfh2, entry_id, true, kDestUrl,
        ui::PAGE_TRANSITION_TYPED);
    EXPECT_FALSE(rfh1->is_active());
    EXPECT_TRUE(rfh2->is_active());

    // The new RVH should be able to update its favicons.
    {
        PluginFaviconMessageObserver observer(contents());
        EXPECT_TRUE(rfh2->GetRenderViewHost()->GetWidget()->OnMessageReceived(
            ViewHostMsg_UpdateFaviconURL(rfh2->GetRenderViewHost()->GetRoutingID(),
                icons)));
        EXPECT_TRUE(observer.favicon_received());
    }

    // The old renderer, being slow, now updates its favicons. The message should
    // be ignored.
    {
        PluginFaviconMessageObserver observer(contents());
        EXPECT_TRUE(rfh1->GetRenderViewHost()->GetWidget()->OnMessageReceived(
            ViewHostMsg_UpdateFaviconURL(rfh1->GetRenderViewHost()->GetRoutingID(),
                icons)));
        EXPECT_FALSE(observer.favicon_received());
    }
}

// Test if RenderViewHost::GetRenderWidgetHosts() only returns active
// widgets.
TEST_F(RenderFrameHostManagerTest, GetRenderWidgetHostsReturnsActiveViews)
{
    CreateSwappedOutRenderViewHost();
    std::unique_ptr<RenderWidgetHostIterator> widgets(
        RenderWidgetHost::GetRenderWidgetHosts());

    // We know that there is the only one active widget. Another view is
    // now swapped out, so the swapped out view is not included in the
    // list.
    RenderWidgetHost* widget = widgets->GetNextHost();
    EXPECT_FALSE(widgets->GetNextHost());
    RenderViewHost* rvh = RenderViewHost::From(widget);
    EXPECT_TRUE(static_cast<RenderViewHostImpl*>(rvh)->is_active());
}

// Test if RenderViewHost::GetRenderWidgetHosts() returns a subset of
// RenderViewHostImpl::GetAllRenderWidgetHosts().
// RenderViewHost::GetRenderWidgetHosts() returns only active widgets, but
// RenderViewHostImpl::GetAllRenderWidgetHosts() returns everything
// including swapped out ones.
TEST_F(RenderFrameHostManagerTest,
    GetRenderWidgetHostsWithinGetAllRenderWidgetHosts)
{
    CreateSwappedOutRenderViewHost();
    std::unique_ptr<RenderWidgetHostIterator> widgets(
        RenderWidgetHost::GetRenderWidgetHosts());

    while (RenderWidgetHost* w = widgets->GetNextHost()) {
        bool found = false;
        std::unique_ptr<RenderWidgetHostIterator> all_widgets(
            RenderWidgetHostImpl::GetAllRenderWidgetHosts());
        while (RenderWidgetHost* widget = all_widgets->GetNextHost()) {
            if (w == widget) {
                found = true;
                break;
            }
        }
        EXPECT_TRUE(found);
    }
}

// Test if SiteInstanceImpl::active_frame_count() is correctly updated
// as frames in a SiteInstance get swapped out and in.
TEST_F(RenderFrameHostManagerTest, ActiveFrameCountWhileSwappingInAndOut)
{
    const GURL kUrl1("http://www.google.com/");
    const GURL kUrl2("http://www.chromium.org/");

    // Navigate to an initial URL.
    contents()->NavigateAndCommit(kUrl1);
    TestRenderFrameHost* rfh1 = main_test_rfh();

    SiteInstanceImpl* instance1 = rfh1->GetSiteInstance();
    EXPECT_EQ(instance1->active_frame_count(), 1U);

    // Create 2 new tabs and simulate them being the opener chain for the main
    // tab.  They should be in the same SiteInstance.
    std::unique_ptr<TestWebContents> opener1(
        TestWebContents::Create(browser_context(), instance1));
    contents()->SetOpener(opener1.get());

    std::unique_ptr<TestWebContents> opener2(
        TestWebContents::Create(browser_context(), instance1));
    opener1->SetOpener(opener2.get());

    EXPECT_EQ(instance1->active_frame_count(), 3U);

    // Navigate to a cross-site URL (different SiteInstance but same
    // BrowsingInstance).
    contents()->NavigateAndCommit(kUrl2);
    TestRenderFrameHost* rfh2 = main_test_rfh();
    SiteInstanceImpl* instance2 = rfh2->GetSiteInstance();

    // rvh2 is on chromium.org which is different from google.com on
    // which other tabs are.
    EXPECT_EQ(instance2->active_frame_count(), 1U);

    // There are two active views on google.com now.
    EXPECT_EQ(instance1->active_frame_count(), 2U);

    // Navigate to the original origin (google.com).
    contents()->NavigateAndCommit(kUrl1);

    EXPECT_EQ(instance1->active_frame_count(), 3U);
}

// This deletes a WebContents when the given RVH is deleted. This is
// only for testing whether deleting an RVH does not cause any UaF in
// other parts of the system. For now, this class is only used for the
// next test cases to detect the bug mentioned at
// http://crbug.com/259859.
class RenderViewHostDestroyer : public WebContentsObserver {
public:
    RenderViewHostDestroyer(RenderViewHost* render_view_host,
        WebContents* web_contents)
        : WebContentsObserver(WebContents::FromRenderViewHost(render_view_host))
        , render_view_host_(render_view_host)
        , web_contents_(web_contents)
    {
    }

    void RenderViewDeleted(RenderViewHost* render_view_host) override
    {
        if (render_view_host == render_view_host_)
            delete web_contents_;
    }

private:
    RenderViewHost* render_view_host_;
    WebContents* web_contents_;

    DISALLOW_COPY_AND_ASSIGN(RenderViewHostDestroyer);
};

// Test if ShutdownRenderViewHostsInSiteInstance() does not touch any
// RenderWidget that has been freed while deleting a RenderViewHost in
// a previous iteration. This is a regression test for
// http://crbug.com/259859.
TEST_F(RenderFrameHostManagerTest,
    DetectUseAfterFreeInShutdownRenderViewHostsInSiteInstance)
{
    const GURL kChromeURL("chrome://newtab");
    const GURL kUrl1("http://www.google.com");
    const GURL kUrl2("http://www.chromium.org");

    // Navigate our first tab to a chrome url and then to the destination.
    NavigateActiveAndCommit(kChromeURL);
    TestRenderFrameHost* ntp_rfh = contents()->GetMainFrame();

    // Create one more tab and navigate to kUrl1.  web_contents is not
    // wrapped as scoped_ptr since it intentionally deleted by destroyer
    // below as part of this test.
    TestWebContents* web_contents = TestWebContents::Create(browser_context(), ntp_rfh->GetSiteInstance());
    web_contents->NavigateAndCommit(kUrl1);
    RenderViewHostDestroyer destroyer(ntp_rfh->GetRenderViewHost(),
        web_contents);

    // This causes the first tab to navigate to kUrl2, which destroys
    // the ntp_rfh in ShutdownRenderViewHostsInSiteInstance(). When
    // ntp_rfh is destroyed, it also destroys the RVHs in web_contents
    // too. This can test whether
    // SiteInstanceImpl::ShutdownRenderViewHostsInSiteInstance() can
    // touch any object freed in this way or not while iterating through
    // all widgets.
    contents()->NavigateAndCommit(kUrl2);
}

// When there is an error with the specified page, renderer exits view-source
// mode. See WebFrameImpl::DidFail(). We check by this test that
// EnableViewSourceMode message is sent on every navigation regardless
// RenderView is being newly created or reused.
TEST_F(RenderFrameHostManagerTest, AlwaysSendEnableViewSourceMode)
{
    const GURL kChromeUrl("chrome://foo/");
    const GURL kUrl("http://foo/");
    const GURL kViewSourceUrl("view-source:http://foo/");

    // We have to navigate to some page at first since without this, the first
    // navigation will reuse the SiteInstance created by Init(), and the second
    // one will create a new SiteInstance. Because current_instance and
    // new_instance will be different, a new RenderViewHost will be created for
    // the second navigation. We have to avoid this in order to exercise the
    // target code path.
    NavigateActiveAndCommit(kChromeUrl);

    // Navigate. Note that "view source" URLs are implemented by putting the RFH
    // into a view-source mode and then navigating to the inner URL, so that's why
    // the bare URL is what's committed and returned by the last committed entry's
    // GetURL() call.
    controller().LoadURL(
        kViewSourceUrl, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string());
    int entry_id = controller().GetPendingEntry()->GetUniqueID();

    // Simulate response from RenderFrame for DispatchBeforeUnload.
    contents()->GetMainFrame()->PrepareForCommit();
    ASSERT_TRUE(contents()->GetPendingMainFrame())
        << "Expected new pending RenderFrameHost to be created.";
    RenderFrameHost* last_rfh = contents()->GetPendingMainFrame();
    contents()->GetPendingMainFrame()->SendNavigate(entry_id, true, kUrl);

    EXPECT_EQ(1, controller().GetLastCommittedEntryIndex());
    NavigationEntry* last_committed = controller().GetLastCommittedEntry();
    ASSERT_NE(nullptr, last_committed);
    EXPECT_EQ(kUrl, last_committed->GetURL());
    EXPECT_EQ(kViewSourceUrl, last_committed->GetVirtualURL());
    EXPECT_FALSE(controller().GetPendingEntry());
    // Because we're using TestWebContents and TestRenderViewHost in this
    // unittest, no one calls WebContentsImpl::RenderViewCreated(). So, we see no
    // EnableViewSourceMode message, here.

    // Clear queued messages before load.
    process()->sink().ClearMessages();

    // Navigate, again.
    controller().LoadURL(
        kViewSourceUrl, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string());
    entry_id = controller().GetPendingEntry()->GetUniqueID();
    contents()->GetMainFrame()->PrepareForCommit();

    // The same RenderViewHost should be reused.
    EXPECT_FALSE(contents()->GetPendingMainFrame());
    EXPECT_EQ(last_rfh, contents()->GetMainFrame());

    // The renderer sends a commit.
    contents()->GetMainFrame()->SendNavigateWithTransition(
        entry_id, false, kUrl, ui::PAGE_TRANSITION_TYPED);
    EXPECT_EQ(1, controller().GetLastCommittedEntryIndex());
    EXPECT_FALSE(controller().GetPendingEntry());

    // New message should be sent out to make sure to enter view-source mode.
    EXPECT_TRUE(process()->sink().GetUniqueMessageMatching(
        FrameMsg_EnableViewSourceMode::ID));
}

// Tests the Init function by checking the initial RenderViewHost.
TEST_F(RenderFrameHostManagerTest, Init)
{
    // Using TestBrowserContext.
    scoped_refptr<SiteInstanceImpl> instance = SiteInstanceImpl::Create(browser_context());
    EXPECT_FALSE(instance->HasSite());

    std::unique_ptr<TestWebContents> web_contents(
        TestWebContents::Create(browser_context(), instance));

    RenderFrameHostManager* manager = web_contents->GetRenderManagerForTesting();
    RenderViewHostImpl* rvh = manager->current_host();
    RenderFrameHostImpl* rfh = manager->current_frame_host();
    ASSERT_TRUE(rvh);
    ASSERT_TRUE(rfh);
    EXPECT_EQ(rvh, rfh->render_view_host());
    EXPECT_EQ(instance, rvh->GetSiteInstance());
    EXPECT_EQ(web_contents.get(), rvh->GetDelegate());
    EXPECT_EQ(web_contents.get(), rfh->delegate());
    EXPECT_TRUE(manager->GetRenderWidgetHostView());
    EXPECT_FALSE(manager->pending_render_view_host());
}

// Tests the Navigate function. We navigate three sites consecutively and check
// how the pending/committed RenderViewHost are modified.
TEST_F(RenderFrameHostManagerTest, Navigate)
{
    std::unique_ptr<TestWebContents> web_contents(TestWebContents::Create(
        browser_context(), SiteInstance::Create(browser_context())));
    RenderViewHostChangedObserver change_observer(web_contents.get());

    RenderFrameHostManager* manager = web_contents->GetRenderManagerForTesting();
    RenderFrameHostImpl* host = NULL;

    // 1) The first navigation. --------------------------
    const GURL kUrl1("http://www.google.com/");
    NavigationEntryImpl entry1(
        NULL /* instance */, kUrl1, Referrer(),
        base::string16() /* title */, ui::PAGE_TRANSITION_TYPED,
        false /* is_renderer_init */);
    host = NavigateToEntry(manager, entry1);

    // The RenderFrameHost created in Init will be reused.
    EXPECT_TRUE(host == manager->current_frame_host());
    EXPECT_FALSE(GetPendingFrameHost(manager));

    // Commit.
    manager->DidNavigateFrame(host, true);
    // Commit to SiteInstance should be delayed until RenderFrame commit.
    EXPECT_TRUE(host == manager->current_frame_host());
    ASSERT_TRUE(host);
    EXPECT_FALSE(host->GetSiteInstance()->HasSite());
    host->GetSiteInstance()->SetSite(kUrl1);

    // 2) Navigate to next site. -------------------------
    const GURL kUrl2("http://www.google.com/foo");
    NavigationEntryImpl entry2(
        NULL /* instance */, kUrl2,
        Referrer(kUrl1, blink::WebReferrerPolicyDefault),
        base::string16() /* title */, ui::PAGE_TRANSITION_LINK,
        true /* is_renderer_init */);
    host = NavigateToEntry(manager, entry2);

    // The RenderFrameHost created in Init will be reused.
    EXPECT_TRUE(host == manager->current_frame_host());
    EXPECT_FALSE(GetPendingFrameHost(manager));

    // Commit.
    manager->DidNavigateFrame(host, true);
    EXPECT_TRUE(host == manager->current_frame_host());
    ASSERT_TRUE(host);
    EXPECT_TRUE(host->GetSiteInstance()->HasSite());

    // 3) Cross-site navigate to next site. --------------
    const GURL kUrl3("http://webkit.org/");
    NavigationEntryImpl entry3(
        NULL /* instance */, kUrl3,
        Referrer(kUrl2, blink::WebReferrerPolicyDefault),
        base::string16() /* title */, ui::PAGE_TRANSITION_LINK,
        false /* is_renderer_init */);
    host = NavigateToEntry(manager, entry3);

    // A new RenderFrameHost should be created.
    EXPECT_TRUE(GetPendingFrameHost(manager));
    ASSERT_EQ(host, GetPendingFrameHost(manager));

    change_observer.Reset();

    // Commit.
    manager->DidNavigateFrame(GetPendingFrameHost(manager), true);
    EXPECT_TRUE(host == manager->current_frame_host());
    ASSERT_TRUE(host);
    EXPECT_TRUE(host->GetSiteInstance()->HasSite());
    // Check the pending RenderFrameHost has been committed.
    EXPECT_FALSE(GetPendingFrameHost(manager));

    // We should observe RVH changed event.
    EXPECT_TRUE(change_observer.DidHostChange());
}

// Tests WebUI creation.
TEST_F(RenderFrameHostManagerTest, WebUI)
{
    set_should_create_webui(true);
    scoped_refptr<SiteInstance> instance = SiteInstance::Create(browser_context());

    std::unique_ptr<TestWebContents> web_contents(
        TestWebContents::Create(browser_context(), instance));
    RenderFrameHostManager* manager = web_contents->GetRenderManagerForTesting();
    RenderFrameHostImpl* initial_rfh = manager->current_frame_host();

    EXPECT_FALSE(manager->current_host()->IsRenderViewLive());
    EXPECT_FALSE(manager->current_frame_host()->web_ui());
    EXPECT_TRUE(initial_rfh);

    const GURL kUrl("chrome://foo");
    NavigationEntryImpl entry(NULL /* instance */, kUrl,
        Referrer(), base::string16() /* title */,
        ui::PAGE_TRANSITION_TYPED,
        false /* is_renderer_init */);
    RenderFrameHostImpl* host = NavigateToEntry(manager, entry);

    // We commit the pending RenderFrameHost immediately because the previous
    // RenderFrameHost was not live.  We test a case where it is live in
    // WebUIInNewTab.
    EXPECT_TRUE(host);
    EXPECT_NE(initial_rfh, host);
    EXPECT_EQ(host, manager->current_frame_host());
    EXPECT_FALSE(GetPendingFrameHost(manager));

    // It's important that the SiteInstance get set on the Web UI page as soon
    // as the navigation starts, rather than lazily after it commits, so we don't
    // try to re-use the SiteInstance/process for non Web UI things that may
    // get loaded in between.
    EXPECT_TRUE(host->GetSiteInstance()->HasSite());
    EXPECT_EQ(kUrl, host->GetSiteInstance()->GetSiteURL());

    // The Web UI is committed immediately because the RenderViewHost has not been
    // used yet. UpdateStateForNavigate() took the short cut path.
    if (IsBrowserSideNavigationEnabled()) {
        // In PlzNavigate, there will be a navigating WebUI because
        // GetFrameHostForNavigation was already called twice and the committed
        // WebUI should be set to be reused.
        EXPECT_TRUE(manager->GetNavigatingWebUI());
        EXPECT_EQ(host->web_ui(), manager->GetNavigatingWebUI());
        EXPECT_EQ(host->web_ui(), host->pending_web_ui());
    } else {
        // The WebUI was immediately committed and there should be none navigating.
        EXPECT_FALSE(manager->GetNavigatingWebUI());
    }
    EXPECT_TRUE(manager->current_frame_host()->web_ui());

    // Commit.
    manager->DidNavigateFrame(host, true);
    EXPECT_TRUE(
        host->render_view_host()->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
}

// Tests that we can open a WebUI link in a new tab from a WebUI page and still
// grant the correct bindings.  http://crbug.com/189101.
TEST_F(RenderFrameHostManagerTest, WebUIInNewTab)
{
    set_should_create_webui(true);
    scoped_refptr<SiteInstance> blank_instance = SiteInstance::Create(browser_context());
    blank_instance->GetProcess()->Init();

    // Create a blank tab.
    std::unique_ptr<TestWebContents> web_contents1(
        TestWebContents::Create(browser_context(), blank_instance));
    RenderFrameHostManager* manager1 = web_contents1->GetRenderManagerForTesting();
    // Test the case that new RVH is considered live.
    manager1->current_host()->CreateRenderView(-1, MSG_ROUTING_NONE,
        FrameReplicationState(), false);
    EXPECT_TRUE(manager1->current_host()->IsRenderViewLive());
    EXPECT_TRUE(manager1->current_frame_host()->IsRenderFrameLive());

    // Navigate to a WebUI page.
    const GURL kUrl1("chrome://foo");
    NavigationEntryImpl entry1(NULL /* instance */, kUrl1,
        Referrer(), base::string16() /* title */,
        ui::PAGE_TRANSITION_TYPED,
        false /* is_renderer_init */);
    RenderFrameHostImpl* host1 = NavigateToEntry(manager1, entry1);

    // We should have a pending navigation to the WebUI RenderViewHost.
    // It should already have bindings.
    EXPECT_EQ(host1, GetPendingFrameHost(manager1));
    EXPECT_NE(host1, manager1->current_frame_host());
    EXPECT_TRUE(
        host1->render_view_host()->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);

    // Commit and ensure we still have bindings.
    manager1->DidNavigateFrame(host1, true);
    SiteInstance* webui_instance = host1->GetSiteInstance();
    EXPECT_EQ(host1, manager1->current_frame_host());
    EXPECT_TRUE(
        host1->render_view_host()->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);

    // Now simulate clicking a link that opens in a new tab.
    std::unique_ptr<TestWebContents> web_contents2(
        TestWebContents::Create(browser_context(), webui_instance));
    RenderFrameHostManager* manager2 = web_contents2->GetRenderManagerForTesting();
    // Make sure the new RVH is considered live.  This is usually done in
    // RenderWidgetHost::Init when opening a new tab from a link.
    manager2->current_host()->CreateRenderView(-1, MSG_ROUTING_NONE,
        FrameReplicationState(), false);
    EXPECT_TRUE(manager2->current_host()->IsRenderViewLive());

    const GURL kUrl2("chrome://foo/bar");
    NavigationEntryImpl entry2(NULL /* instance */, kUrl2,
        Referrer(), base::string16() /* title */,
        ui::PAGE_TRANSITION_LINK,
        true /* is_renderer_init */);
    RenderFrameHostImpl* host2 = NavigateToEntry(manager2, entry2);

    // No cross-process transition happens because we are already in the right
    // SiteInstance.  We should grant bindings immediately.
    EXPECT_EQ(host2, manager2->current_frame_host());
    EXPECT_TRUE(manager2->GetNavigatingWebUI());
    EXPECT_FALSE(host2->web_ui());
    EXPECT_TRUE(
        host2->render_view_host()->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);

    manager2->DidNavigateFrame(host2, true);
}

// Tests that a WebUI is correctly reused between chrome:// pages.
TEST_F(RenderFrameHostManagerTest, WebUIWasReused)
{
    set_should_create_webui(true);

    // Navigate to a WebUI page.
    const GURL kUrl1("chrome://foo");
    contents()->NavigateAndCommit(kUrl1);
    WebUIImpl* web_ui = main_test_rfh()->web_ui();
    EXPECT_TRUE(web_ui);

    // Navigate to another WebUI page which should be same-site and keep the
    // current WebUI.
    const GURL kUrl2("chrome://foo/bar");
    contents()->NavigateAndCommit(kUrl2);
    EXPECT_EQ(web_ui, main_test_rfh()->web_ui());
}

// Tests that a WebUI is correctly cleaned up when navigating from a chrome://
// page to a non-chrome:// page.
TEST_F(RenderFrameHostManagerTest, WebUIWasCleared)
{
    set_should_create_webui(true);

    // Navigate to a WebUI page.
    const GURL kUrl1("chrome://foo");
    contents()->NavigateAndCommit(kUrl1);
    EXPECT_TRUE(main_test_rfh()->web_ui());

    // Navigate to a non-WebUI page.
    const GURL kUrl2("http://www.google.com");
    contents()->NavigateAndCommit(kUrl2);
    EXPECT_FALSE(main_test_rfh()->web_ui());
}

// Tests that we don't end up in an inconsistent state if a page does a back and
// then reload. http://crbug.com/51680
// Also tests that only user-gesture navigations can interrupt cross-process
// navigations. http://crbug.com/75195
TEST_F(RenderFrameHostManagerTest, PageDoesBackAndReload)
{
    if (IsBrowserSideNavigationEnabled()) {
        // PlzNavigate uses a significantly different logic for renderer initiated
        // navigations and navigation cancellation. Adapting this test would make it
        // full of special cases and almost unreadable.
        // There are tests that exercise these concerns for PlzNavigate, all from
        // NavigatorTestWithBrowserSideNavigation:
        // - BrowserInitiatedNavigationCancel
        // - RendererUserInitiatedNavigationCancel
        // - RendererNonUserInitiatedNavigationDoesntCancelRendererUserInitiated
        // - RendererNonUserInitiatedNavigationDoesntCancelBrowserInitiated
        // - RendererNonUserInitiatedNavigationCancelSimilarNavigation
        SUCCEED() << "Test is not applicable with browser side navigation enabled";
        return;
    }
    const GURL kUrl1("http://www.google.com/");
    const GURL kUrl2("http://www.evil-site.com/");

    // Navigate to a safe site, then an evil site.
    // This will switch RenderFrameHosts.  We cannot assert that the first and
    // second RFHs are different, though, because the first one may be promptly
    // deleted.
    contents()->NavigateAndCommit(kUrl1);
    contents()->NavigateAndCommit(kUrl2);
    TestRenderFrameHost* evil_rfh = contents()->GetMainFrame();

    // Now let's simulate the evil page calling history.back().
    contents()->OnGoToEntryAtOffset(evil_rfh->GetRenderViewHost(), -1);
    contents()->GetMainFrame()->PrepareForCommit();
    // We should have a new pending RFH.
    // Note that in this case, the navigation has not committed, so evil_rfh will
    // not be deleted yet.
    EXPECT_NE(evil_rfh, contents()->GetPendingMainFrame());
    EXPECT_NE(evil_rfh->GetRenderViewHost(),
        contents()->GetPendingMainFrame()->GetRenderViewHost());

    // Before that RFH has committed, the evil page reloads itself.
    FrameHostMsg_DidCommitProvisionalLoad_Params params;
    params.nav_entry_id = 0;
    params.did_create_new_entry = false;
    params.url = kUrl2;
    params.transition = ui::PAGE_TRANSITION_CLIENT_REDIRECT;
    params.should_update_history = false;
    params.gesture = NavigationGestureAuto;
    params.was_within_same_page = false;
    params.method = "GET";
    params.page_state = PageState::CreateFromURL(kUrl2);

    evil_rfh->SimulateNavigationStart(kUrl2);
    evil_rfh->SendNavigateWithParams(&params);
    evil_rfh->SimulateNavigationStop();

    // That should NOT have cancelled the pending RFH, because the reload did
    // not have a user gesture. Thus, the pending back navigation will still
    // eventually commit.
    EXPECT_TRUE(contents()->GetRenderManagerForTesting()->pending_render_view_host() != NULL);
    EXPECT_TRUE(contents()->GetRenderManagerForTesting()->pending_frame_host() != NULL);
    EXPECT_EQ(evil_rfh,
        contents()->GetRenderManagerForTesting()->current_frame_host());
    EXPECT_EQ(evil_rfh->GetRenderViewHost(),
        contents()->GetRenderManagerForTesting()->current_host());

    // Also we should not have a pending navigation entry.
    EXPECT_TRUE(contents()->GetController().GetPendingEntry() == NULL);
    NavigationEntry* entry = contents()->GetController().GetVisibleEntry();
    ASSERT_TRUE(entry != NULL);
    EXPECT_EQ(kUrl2, entry->GetURL());

    // Now do the same but as a user gesture.
    params.gesture = NavigationGestureUser;
    evil_rfh->SimulateNavigationStart(kUrl2);
    evil_rfh->SendNavigateWithParams(&params);
    evil_rfh->SimulateNavigationStop();

    // User navigation should have cancelled the pending RFH.
    EXPECT_TRUE(contents()->GetRenderManagerForTesting()->pending_render_view_host() == NULL);
    EXPECT_TRUE(contents()->GetRenderManagerForTesting()->pending_frame_host() == NULL);
    EXPECT_EQ(evil_rfh,
        contents()->GetRenderManagerForTesting()->current_frame_host());
    EXPECT_EQ(evil_rfh->GetRenderViewHost(),
        contents()->GetRenderManagerForTesting()->current_host());

    // Also we should not have a pending navigation entry.
    EXPECT_TRUE(contents()->GetController().GetPendingEntry() == NULL);
    entry = contents()->GetController().GetVisibleEntry();
    ASSERT_TRUE(entry != NULL);
    EXPECT_EQ(kUrl2, entry->GetURL());
}

// Ensure that we can go back and forward even if a SwapOut ACK isn't received.
// See http://crbug.com/93427.
TEST_F(RenderFrameHostManagerTest, NavigateAfterMissingSwapOutACK)
{
    const GURL kUrl1("http://www.google.com/");
    const GURL kUrl2("http://www.chromium.org/");

    // Navigate to two pages.
    contents()->NavigateAndCommit(kUrl1);
    TestRenderFrameHost* rfh1 = main_test_rfh();

    // Keep active_frame_count nonzero so that no swapped out frames in
    // this SiteInstance get forcefully deleted.
    rfh1->GetSiteInstance()->IncrementActiveFrameCount();

    contents()->NavigateAndCommit(kUrl2);
    TestRenderFrameHost* rfh2 = main_test_rfh();
    rfh2->GetSiteInstance()->IncrementActiveFrameCount();

    // Now go back, but suppose the SwapOut_ACK isn't received.  This shouldn't
    // happen, but we have seen it when going back quickly across many entries
    // (http://crbug.com/93427).
    contents()->GetController().GoBack();
    EXPECT_TRUE(rfh2->is_waiting_for_beforeunload_ack());
    contents()->GetMainFrame()->PrepareForCommit();
    EXPECT_FALSE(rfh2->is_waiting_for_beforeunload_ack());

    // The back navigation commits.
    const NavigationEntry* entry1 = contents()->GetController().GetPendingEntry();
    contents()->GetPendingMainFrame()->SendNavigate(
        entry1->GetUniqueID(), false, entry1->GetURL());
    EXPECT_TRUE(rfh2->IsWaitingForUnloadACK());
    EXPECT_FALSE(rfh2->is_active());

    // We should be able to navigate forward.
    contents()->GetController().GoForward();
    contents()->GetMainFrame()->PrepareForCommit();
    const NavigationEntry* entry2 = contents()->GetController().GetPendingEntry();
    contents()->GetPendingMainFrame()->SendNavigate(
        entry2->GetUniqueID(), false, entry2->GetURL());
    EXPECT_TRUE(main_test_rfh()->is_active());
}

// Test that we create swapped out RFHs for the opener chain when navigating an
// opened tab cross-process.  This allows us to support certain cross-process
// JavaScript calls (http://crbug.com/99202).
TEST_F(RenderFrameHostManagerTest, CreateSwappedOutOpenerRFHs)
{
    const GURL kUrl1("http://www.google.com/");
    const GURL kUrl2("http://www.chromium.org/");
    const GURL kChromeUrl("chrome://foo");

    // Navigate to an initial URL.
    contents()->NavigateAndCommit(kUrl1);
    RenderFrameHostManager* manager = contents()->GetRenderManagerForTesting();
    TestRenderFrameHost* rfh1 = main_test_rfh();
    scoped_refptr<SiteInstanceImpl> site_instance1 = rfh1->GetSiteInstance();
    RenderFrameDeletedObserver rfh1_deleted_observer(rfh1);
    TestRenderViewHost* rvh1 = test_rvh();

    // Create 2 new tabs and simulate them being the opener chain for the main
    // tab.  They should be in the same SiteInstance.
    std::unique_ptr<TestWebContents> opener1(
        TestWebContents::Create(browser_context(), site_instance1.get()));
    RenderFrameHostManager* opener1_manager = opener1->GetRenderManagerForTesting();
    contents()->SetOpener(opener1.get());

    std::unique_ptr<TestWebContents> opener2(
        TestWebContents::Create(browser_context(), site_instance1.get()));
    RenderFrameHostManager* opener2_manager = opener2->GetRenderManagerForTesting();
    opener1->SetOpener(opener2.get());

    // Navigate to a cross-site URL (different SiteInstance but same
    // BrowsingInstance).
    contents()->NavigateAndCommit(kUrl2);
    TestRenderFrameHost* rfh2 = main_test_rfh();
    TestRenderViewHost* rvh2 = test_rvh();
    EXPECT_NE(site_instance1, rfh2->GetSiteInstance());
    EXPECT_TRUE(site_instance1->IsRelatedSiteInstance(rfh2->GetSiteInstance()));

    // Ensure rvh1 is placed on swapped out list of the current tab.
    EXPECT_TRUE(rfh1_deleted_observer.deleted());
    EXPECT_TRUE(manager->GetRenderFrameProxyHost(site_instance1.get()));
    EXPECT_EQ(rvh1,
        manager->GetSwappedOutRenderViewHost(rvh1->GetSiteInstance()));

    // Ensure a proxy and swapped out RVH are created in the first opener tab.
    EXPECT_TRUE(
        opener1_manager->GetRenderFrameProxyHost(rfh2->GetSiteInstance()));
    TestRenderViewHost* opener1_rvh = static_cast<TestRenderViewHost*>(
        opener1_manager->GetSwappedOutRenderViewHost(rvh2->GetSiteInstance()));
    EXPECT_FALSE(opener1_rvh->is_active());

    // Ensure a proxy and swapped out RVH are created in the second opener tab.
    EXPECT_TRUE(
        opener2_manager->GetRenderFrameProxyHost(rfh2->GetSiteInstance()));
    TestRenderViewHost* opener2_rvh = static_cast<TestRenderViewHost*>(
        opener2_manager->GetSwappedOutRenderViewHost(rvh2->GetSiteInstance()));
    EXPECT_FALSE(opener2_rvh->is_active());

    // Navigate to a cross-BrowsingInstance URL.
    contents()->NavigateAndCommit(kChromeUrl);
    TestRenderFrameHost* rfh3 = main_test_rfh();
    EXPECT_NE(site_instance1, rfh3->GetSiteInstance());
    EXPECT_FALSE(site_instance1->IsRelatedSiteInstance(rfh3->GetSiteInstance()));

    // No scripting is allowed across BrowsingInstances, so we should not create
    // swapped out RVHs for the opener chain in this case.
    EXPECT_FALSE(opener1_manager->GetRenderFrameProxyHost(
        rfh3->GetSiteInstance()));
    EXPECT_FALSE(opener1_manager->GetSwappedOutRenderViewHost(
        rfh3->GetSiteInstance()));
    EXPECT_FALSE(opener2_manager->GetRenderFrameProxyHost(
        rfh3->GetSiteInstance()));
    EXPECT_FALSE(opener2_manager->GetSwappedOutRenderViewHost(
        rfh3->GetSiteInstance()));
}

// Test that a page can disown the opener of the WebContents.
TEST_F(RenderFrameHostManagerTest, DisownOpener)
{
    const GURL kUrl1("http://www.google.com/");
    const GURL kUrl2("http://www.chromium.org/");

    // Navigate to an initial URL.
    contents()->NavigateAndCommit(kUrl1);
    TestRenderFrameHost* rfh1 = main_test_rfh();
    scoped_refptr<SiteInstanceImpl> site_instance1 = rfh1->GetSiteInstance();

    // Create a new tab and simulate having it be the opener for the main tab.
    std::unique_ptr<TestWebContents> opener1(
        TestWebContents::Create(browser_context(), rfh1->GetSiteInstance()));
    contents()->SetOpener(opener1.get());
    EXPECT_TRUE(contents()->HasOpener());

    // Navigate to a cross-site URL (different SiteInstance but same
    // BrowsingInstance).
    contents()->NavigateAndCommit(kUrl2);
    TestRenderFrameHost* rfh2 = main_test_rfh();
    EXPECT_NE(site_instance1, rfh2->GetSiteInstance());

    // Disown the opener from rfh2.
    rfh2->DidChangeOpener(MSG_ROUTING_NONE);

    // Ensure the opener is cleared.
    EXPECT_FALSE(contents()->HasOpener());
}

// Test that a page can disown a same-site opener of the WebContents.
TEST_F(RenderFrameHostManagerTest, DisownSameSiteOpener)
{
    const GURL kUrl1("http://www.google.com/");

    // Navigate to an initial URL.
    contents()->NavigateAndCommit(kUrl1);
    TestRenderFrameHost* rfh1 = main_test_rfh();

    // Create a new tab and simulate having it be the opener for the main tab.
    std::unique_ptr<TestWebContents> opener1(
        TestWebContents::Create(browser_context(), rfh1->GetSiteInstance()));
    contents()->SetOpener(opener1.get());
    EXPECT_TRUE(contents()->HasOpener());

    // Disown the opener from rfh1.
    rfh1->DidChangeOpener(MSG_ROUTING_NONE);

    // Ensure the opener is cleared even if it is in the same process.
    EXPECT_FALSE(contents()->HasOpener());
}

// Test that a page can disown the opener just as a cross-process navigation is
// in progress.
TEST_F(RenderFrameHostManagerTest, DisownOpenerDuringNavigation)
{
    const GURL kUrl1("http://www.google.com/");
    const GURL kUrl2("http://www.chromium.org/");

    // Navigate to an initial URL.
    contents()->NavigateAndCommit(kUrl1);
    scoped_refptr<SiteInstanceImpl> site_instance1 = main_test_rfh()->GetSiteInstance();

    // Create a new tab and simulate having it be the opener for the main tab.
    std::unique_ptr<TestWebContents> opener1(
        TestWebContents::Create(browser_context(), site_instance1.get()));
    contents()->SetOpener(opener1.get());
    EXPECT_TRUE(contents()->HasOpener());

    // Navigate to a cross-site URL (different SiteInstance but same
    // BrowsingInstance).
    contents()->NavigateAndCommit(kUrl2);
    TestRenderFrameHost* rfh2 = main_test_rfh();
    EXPECT_NE(site_instance1, rfh2->GetSiteInstance());

    // Start a back navigation.
    contents()->GetController().GoBack();
    contents()->GetMainFrame()->PrepareForCommit();

    // Disown the opener from rfh2.
    rfh2->DidChangeOpener(MSG_ROUTING_NONE);

    // Ensure the opener is cleared.
    EXPECT_FALSE(contents()->HasOpener());

    // The back navigation commits.
    const NavigationEntry* entry1 = contents()->GetController().GetPendingEntry();
    contents()->GetPendingMainFrame()->SendNavigate(
        entry1->GetUniqueID(), false, entry1->GetURL());

    // Ensure the opener is still cleared.
    EXPECT_FALSE(contents()->HasOpener());
}

// Test that a page can disown the opener just after a cross-process navigation
// commits.
TEST_F(RenderFrameHostManagerTest, DisownOpenerAfterNavigation)
{
    const GURL kUrl1("http://www.google.com/");
    const GURL kUrl2("http://www.chromium.org/");

    // Navigate to an initial URL.
    contents()->NavigateAndCommit(kUrl1);
    scoped_refptr<SiteInstanceImpl> site_instance1 = main_test_rfh()->GetSiteInstance();

    // Create a new tab and simulate having it be the opener for the main tab.
    std::unique_ptr<TestWebContents> opener1(
        TestWebContents::Create(browser_context(), site_instance1.get()));
    contents()->SetOpener(opener1.get());
    EXPECT_TRUE(contents()->HasOpener());

    // Navigate to a cross-site URL (different SiteInstance but same
    // BrowsingInstance).
    contents()->NavigateAndCommit(kUrl2);
    TestRenderFrameHost* rfh2 = main_test_rfh();
    EXPECT_NE(site_instance1, rfh2->GetSiteInstance());

    // Commit a back navigation before the DidChangeOpener message arrives.
    contents()->GetController().GoBack();
    contents()->GetMainFrame()->PrepareForCommit();
    const NavigationEntry* entry1 = contents()->GetController().GetPendingEntry();
    contents()->GetPendingMainFrame()->SendNavigate(
        entry1->GetUniqueID(), false, entry1->GetURL());

    // Disown the opener from rfh2.
    rfh2->DidChangeOpener(MSG_ROUTING_NONE);
    EXPECT_FALSE(contents()->HasOpener());
}

// Test that we clean up swapped out RenderViewHosts when a process hosting
// those associated RenderViews crashes. http://crbug.com/258993
TEST_F(RenderFrameHostManagerTest, CleanUpSwappedOutRVHOnProcessCrash)
{
    const GURL kUrl1("http://www.google.com/");
    const GURL kUrl2("http://www.chromium.org/");

    // Navigate to an initial URL.
    contents()->NavigateAndCommit(kUrl1);
    TestRenderFrameHost* rfh1 = contents()->GetMainFrame();

    // Create a new tab as an opener for the main tab.
    std::unique_ptr<TestWebContents> opener1(
        TestWebContents::Create(browser_context(), rfh1->GetSiteInstance()));
    RenderFrameHostManager* opener1_manager = opener1->GetRenderManagerForTesting();
    contents()->SetOpener(opener1.get());

    // Make sure the new opener RVH is considered live.
    opener1_manager->current_host()->CreateRenderView(
        -1, MSG_ROUTING_NONE, FrameReplicationState(), false);
    EXPECT_TRUE(opener1_manager->current_host()->IsRenderViewLive());
    EXPECT_TRUE(opener1_manager->current_frame_host()->IsRenderFrameLive());

    // Use a cross-process navigation in the opener to swap out the old RVH.
    EXPECT_FALSE(
        opener1_manager->GetSwappedOutRenderViewHost(rfh1->GetSiteInstance()));
    opener1->NavigateAndCommit(kUrl2);
    RenderViewHostImpl* swapped_out_rvh = opener1_manager->GetSwappedOutRenderViewHost(rfh1->GetSiteInstance());
    EXPECT_TRUE(swapped_out_rvh);
    EXPECT_TRUE(swapped_out_rvh->is_swapped_out_);
    EXPECT_FALSE(swapped_out_rvh->is_active());

    // Fake a process crash.
    rfh1->GetProcess()->SimulateCrash();

    // Ensure that the RenderFrameProxyHost stays around and the RenderFrameProxy
    // is deleted.
    RenderFrameProxyHost* render_frame_proxy_host = opener1_manager->GetRenderFrameProxyHost(rfh1->GetSiteInstance());
    EXPECT_TRUE(render_frame_proxy_host);
    EXPECT_FALSE(render_frame_proxy_host->is_render_frame_proxy_live());

    // Expect the swapped out RVH to exist but not be live.
    EXPECT_TRUE(
        opener1_manager->GetSwappedOutRenderViewHost(rfh1->GetSiteInstance()));
    EXPECT_FALSE(
        opener1_manager->GetSwappedOutRenderViewHost(rfh1->GetSiteInstance())
            ->IsRenderViewLive());

    // Reload the initial tab. This should recreate the opener's swapped out RVH
    // in the original SiteInstance.
    contents()->GetController().Reload(ReloadType::NORMAL, true);
    contents()->GetMainFrame()->PrepareForCommit();
    EXPECT_TRUE(
        opener1_manager->GetSwappedOutRenderViewHost(rfh1->GetSiteInstance())
            ->IsRenderViewLive());
    EXPECT_EQ(
        opener1_manager->GetRoutingIdForSiteInstance(rfh1->GetSiteInstance()),
        contents()->GetMainFrame()->GetRenderViewHost()->opener_frame_route_id());
}

// Test that RenderViewHosts created for WebUI navigations are properly
// granted WebUI bindings even if an unprivileged swapped out RenderViewHost
// is in the same process (http://crbug.com/79918).
TEST_F(RenderFrameHostManagerTest, EnableWebUIWithSwappedOutOpener)
{
    set_should_create_webui(true);
    const GURL kSettingsUrl("chrome://chrome/settings");
    const GURL kPluginUrl("chrome://plugins");

    // Navigate to an initial WebUI URL.
    contents()->NavigateAndCommit(kSettingsUrl);

    // Ensure the RVH has WebUI bindings.
    TestRenderViewHost* rvh1 = test_rvh();
    EXPECT_TRUE(rvh1->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);

    // Create a new tab and simulate it being the opener for the main
    // tab.  It should be in the same SiteInstance.
    std::unique_ptr<TestWebContents> opener1(
        TestWebContents::Create(browser_context(), rvh1->GetSiteInstance()));
    RenderFrameHostManager* opener1_manager = opener1->GetRenderManagerForTesting();
    contents()->SetOpener(opener1.get());

    // Navigate to a different WebUI URL (different SiteInstance, same
    // BrowsingInstance).
    contents()->NavigateAndCommit(kPluginUrl);
    TestRenderViewHost* rvh2 = test_rvh();
    EXPECT_NE(rvh1->GetSiteInstance(), rvh2->GetSiteInstance());
    EXPECT_TRUE(rvh1->GetSiteInstance()->IsRelatedSiteInstance(
        rvh2->GetSiteInstance()));

    // Ensure a proxy and swapped out RVH are created in the first opener tab.
    EXPECT_TRUE(
        opener1_manager->GetRenderFrameProxyHost(rvh2->GetSiteInstance()));
    TestRenderViewHost* opener1_rvh = static_cast<TestRenderViewHost*>(
        opener1_manager->GetSwappedOutRenderViewHost(rvh2->GetSiteInstance()));
    EXPECT_FALSE(opener1_rvh->is_active());

    // Ensure the new RVH has WebUI bindings.
    EXPECT_TRUE(rvh2->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
}

// Test that we reuse the same guest SiteInstance if we navigate across sites.
TEST_F(RenderFrameHostManagerTest, NoSwapOnGuestNavigations)
{
    GURL guest_url(std::string(kGuestScheme).append("://abc123"));
    scoped_refptr<SiteInstance> instance = SiteInstance::CreateForURL(browser_context(), guest_url);
    std::unique_ptr<TestWebContents> web_contents(
        TestWebContents::Create(browser_context(), instance));

    RenderFrameHostManager* manager = web_contents->GetRenderManagerForTesting();

    RenderFrameHostImpl* host = NULL;

    // 1) The first navigation. --------------------------
    const GURL kUrl1("http://www.google.com/");
    NavigationEntryImpl entry1(
        NULL /* instance */, kUrl1, Referrer(),
        base::string16() /* title */, ui::PAGE_TRANSITION_TYPED,
        false /* is_renderer_init */);
    host = NavigateToEntry(manager, entry1);

    // The RenderFrameHost created in Init will be reused.
    EXPECT_TRUE(host == manager->current_frame_host());
    EXPECT_FALSE(manager->pending_frame_host());
    EXPECT_EQ(manager->current_frame_host()->GetSiteInstance(), instance);

    // Commit.
    manager->DidNavigateFrame(host, true);
    // Commit to SiteInstance should be delayed until RenderFrame commit.
    EXPECT_EQ(host, manager->current_frame_host());
    ASSERT_TRUE(host);
    EXPECT_TRUE(host->GetSiteInstance()->HasSite());

    // 2) Navigate to a different domain. -------------------------
    // Guests stay in the same process on navigation.
    const GURL kUrl2("http://www.chromium.org");
    NavigationEntryImpl entry2(
        NULL /* instance */, kUrl2,
        Referrer(kUrl1, blink::WebReferrerPolicyDefault),
        base::string16() /* title */, ui::PAGE_TRANSITION_LINK,
        true /* is_renderer_init */);
    host = NavigateToEntry(manager, entry2);

    // The RenderFrameHost created in Init will be reused.
    EXPECT_EQ(host, manager->current_frame_host());
    EXPECT_FALSE(manager->pending_frame_host());

    // Commit.
    manager->DidNavigateFrame(host, true);
    EXPECT_EQ(host, manager->current_frame_host());
    ASSERT_TRUE(host);
    EXPECT_EQ(host->GetSiteInstance(), instance);
}

// Test that we cancel a pending RVH if we close the tab while it's pending.
// http://crbug.com/294697.
TEST_F(RenderFrameHostManagerTest, NavigateWithEarlyClose)
{
    TestNotificationTracker notifications;

    scoped_refptr<SiteInstance> instance = SiteInstance::Create(browser_context());

    BeforeUnloadFiredWebContentsDelegate delegate;
    std::unique_ptr<TestWebContents> web_contents(
        TestWebContents::Create(browser_context(), instance));
    RenderViewHostChangedObserver change_observer(web_contents.get());
    web_contents->SetDelegate(&delegate);

    RenderFrameHostManager* manager = web_contents->GetRenderManagerForTesting();

    // 1) The first navigation. --------------------------
    const GURL kUrl1("http://www.google.com/");
    NavigationEntryImpl entry1(NULL /* instance */, kUrl1,
        Referrer(), base::string16() /* title */,
        ui::PAGE_TRANSITION_TYPED,
        false /* is_renderer_init */);
    RenderFrameHostImpl* host = NavigateToEntry(manager, entry1);

    // The RenderFrameHost created in Init will be reused.
    EXPECT_EQ(host, manager->current_frame_host());
    EXPECT_FALSE(GetPendingFrameHost(manager));

    // We should observe RVH changed event.
    EXPECT_TRUE(change_observer.DidHostChange());

    // Commit.
    manager->DidNavigateFrame(host, true);

    // Commit to SiteInstance should be delayed until RenderFrame commits.
    EXPECT_EQ(host, manager->current_frame_host());
    EXPECT_FALSE(host->GetSiteInstance()->HasSite());
    host->GetSiteInstance()->SetSite(kUrl1);

    // 2) Cross-site navigate to next site. -------------------------
    const GURL kUrl2("http://www.example.com");
    NavigationEntryImpl entry2(
        NULL /* instance */, kUrl2, Referrer(),
        base::string16() /* title */, ui::PAGE_TRANSITION_TYPED,
        false /* is_renderer_init */);
    RenderFrameHostImpl* host2 = NavigateToEntry(manager, entry2);

    // A new RenderFrameHost should be created.
    ASSERT_EQ(host2, GetPendingFrameHost(manager));
    EXPECT_NE(host2, host);

    EXPECT_EQ(host, manager->current_frame_host());
    EXPECT_EQ(host2, GetPendingFrameHost(manager));

    // 3) Close the tab. -------------------------
    notifications.ListenFor(
        NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED,
        Source<RenderWidgetHost>(host2->render_view_host()->GetWidget()));
    manager->OnBeforeUnloadACK(false, true, base::TimeTicks());

    EXPECT_TRUE(
        notifications.Check1AndReset(NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED));
    EXPECT_FALSE(GetPendingFrameHost(manager));
    EXPECT_EQ(host, manager->current_frame_host());
}

TEST_F(RenderFrameHostManagerTest, CloseWithPendingWhileUnresponsive)
{
    const GURL kUrl1("http://www.google.com/");
    const GURL kUrl2("http://www.chromium.org/");

    CloseWebContentsDelegate close_delegate;
    contents()->SetDelegate(&close_delegate);

    // Navigate to the first page.
    contents()->NavigateAndCommit(kUrl1);
    TestRenderFrameHost* rfh1 = contents()->GetMainFrame();

    // Start to close the tab, but assume it's unresponsive.
    rfh1->render_view_host()->ClosePage();
    EXPECT_TRUE(rfh1->render_view_host()->is_waiting_for_close_ack());

    // Start a navigation to a new site.
    controller().LoadURL(
        kUrl2, Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
    if (IsBrowserSideNavigationEnabled())
        rfh1->PrepareForCommit();
    EXPECT_TRUE(contents()->CrossProcessNavigationPending());

    // Simulate the unresponsiveness timer.  The tab should close.
    contents()->RendererUnresponsive(
        rfh1->render_view_host()->GetWidget(),
        RendererUnresponsiveType::RENDERER_UNRESPONSIVE_CLOSE_PAGE);
    EXPECT_TRUE(close_delegate.is_closed());
}

// Tests that the RenderFrameHost is properly deleted when the SwapOutACK is
// received.  (SwapOut and the corresponding ACK always occur after commit.)
// Also tests that an early SwapOutACK is properly ignored.
TEST_F(RenderFrameHostManagerTest, DeleteFrameAfterSwapOutACK)
{
    const GURL kUrl1("http://www.google.com/");
    const GURL kUrl2("http://www.chromium.org/");

    // Navigate to the first page.
    contents()->NavigateAndCommit(kUrl1);
    TestRenderFrameHost* rfh1 = contents()->GetMainFrame();
    RenderFrameDeletedObserver rfh_deleted_observer(rfh1);
    EXPECT_TRUE(rfh1->is_active());

    // Navigate to new site, simulating onbeforeunload approval.
    controller().LoadURL(
        kUrl2, Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
    int entry_id = controller().GetPendingEntry()->GetUniqueID();
    contents()->GetMainFrame()->PrepareForCommit();
    EXPECT_TRUE(contents()->CrossProcessNavigationPending());
    EXPECT_TRUE(rfh1->is_active());
    TestRenderFrameHost* rfh2 = contents()->GetPendingMainFrame();

    // Simulate the swap out ack, unexpectedly early (before commit).  It should
    // have no effect.
    rfh1->OnSwappedOut();
    EXPECT_TRUE(contents()->CrossProcessNavigationPending());
    EXPECT_TRUE(rfh1->is_active());

    // The new page commits.
    contents()->TestDidNavigate(rfh2, entry_id, true, kUrl2,
        ui::PAGE_TRANSITION_TYPED);
    EXPECT_FALSE(contents()->CrossProcessNavigationPending());
    EXPECT_EQ(rfh2, contents()->GetMainFrame());
    EXPECT_TRUE(contents()->GetPendingMainFrame() == NULL);
    EXPECT_TRUE(rfh2->is_active());
    EXPECT_FALSE(rfh1->is_active());

    // Simulate the swap out ack.
    rfh1->OnSwappedOut();

    // rfh1 should have been deleted.
    EXPECT_TRUE(rfh_deleted_observer.deleted());
    rfh1 = NULL;
}

// Tests that the RenderFrameHost is properly swapped out when the SwapOut ACK
// is received.  (SwapOut and the corresponding ACK always occur after commit.)
TEST_F(RenderFrameHostManagerTest, SwapOutFrameAfterSwapOutACK)
{
    const GURL kUrl1("http://www.google.com/");
    const GURL kUrl2("http://www.chromium.org/");

    // Navigate to the first page.
    contents()->NavigateAndCommit(kUrl1);
    TestRenderFrameHost* rfh1 = contents()->GetMainFrame();
    RenderFrameDeletedObserver rfh_deleted_observer(rfh1);
    EXPECT_TRUE(rfh1->is_active());

    // Increment the number of active frames in SiteInstanceImpl so that rfh1 is
    // not deleted on swap out.
    rfh1->GetSiteInstance()->IncrementActiveFrameCount();

    // Navigate to new site, simulating onbeforeunload approval.
    controller().LoadURL(
        kUrl2, Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
    int entry_id = controller().GetPendingEntry()->GetUniqueID();
    contents()->GetMainFrame()->PrepareForCommit();
    EXPECT_TRUE(contents()->CrossProcessNavigationPending());
    EXPECT_TRUE(rfh1->is_active());
    TestRenderFrameHost* rfh2 = contents()->GetPendingMainFrame();

    // The new page commits.
    contents()->TestDidNavigate(rfh2, entry_id, true, kUrl2,
        ui::PAGE_TRANSITION_TYPED);
    EXPECT_FALSE(contents()->CrossProcessNavigationPending());
    EXPECT_EQ(rfh2, contents()->GetMainFrame());
    EXPECT_TRUE(contents()->GetPendingMainFrame() == NULL);
    EXPECT_FALSE(rfh1->is_active());
    EXPECT_TRUE(rfh2->is_active());

    // Simulate the swap out ack.
    rfh1->OnSwappedOut();

    // rfh1 should be deleted.
    EXPECT_TRUE(rfh_deleted_observer.deleted());
}

// Test that the RenderViewHost is properly swapped out if a navigation in the
// new renderer commits before sending the SwapOut message to the old renderer.
// This simulates a cross-site navigation to a synchronously committing URL
// (e.g., a data URL) and ensures it works properly.
TEST_F(RenderFrameHostManagerTest,
    CommitNewNavigationBeforeSendingSwapOut)
{
    const GURL kUrl1("http://www.google.com/");
    const GURL kUrl2("http://www.chromium.org/");

    // Navigate to the first page.
    contents()->NavigateAndCommit(kUrl1);
    TestRenderFrameHost* rfh1 = contents()->GetMainFrame();
    RenderFrameDeletedObserver rfh_deleted_observer(rfh1);
    EXPECT_TRUE(rfh1->is_active());

    // Increment the number of active frames in SiteInstanceImpl so that rfh1 is
    // not deleted on swap out.
    scoped_refptr<SiteInstanceImpl> site_instance = rfh1->GetSiteInstance();
    site_instance->IncrementActiveFrameCount();

    // Navigate to new site, simulating onbeforeunload approval.
    controller().LoadURL(
        kUrl2, Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
    int entry_id = controller().GetPendingEntry()->GetUniqueID();
    rfh1->PrepareForCommit();
    EXPECT_TRUE(contents()->CrossProcessNavigationPending());
    TestRenderFrameHost* rfh2 = contents()->GetPendingMainFrame();

    // The new page commits.
    contents()->TestDidNavigate(rfh2, entry_id, true, kUrl2,
        ui::PAGE_TRANSITION_TYPED);
    EXPECT_FALSE(contents()->CrossProcessNavigationPending());
    EXPECT_EQ(rfh2, contents()->GetMainFrame());
    EXPECT_TRUE(contents()->GetPendingMainFrame() == NULL);
    EXPECT_FALSE(rfh1->is_active());
    EXPECT_TRUE(rfh2->is_active());

    // Simulate the swap out ack.
    rfh1->OnSwappedOut();

    // rfh1 should be deleted.
    EXPECT_TRUE(rfh_deleted_observer.deleted());
    EXPECT_TRUE(contents()->GetFrameTree()->root()->render_manager()->GetRenderFrameProxyHost(site_instance.get()));
}

// Test that a RenderFrameHost is properly deleted when a cross-site navigation
// is cancelled.
TEST_F(RenderFrameHostManagerTest,
    CancelPendingProperlyDeletesOrSwaps)
{
    const GURL kUrl1("http://www.google.com/");
    const GURL kUrl2("http://www.chromium.org/");
    RenderFrameHostImpl* pending_rfh = NULL;
    base::TimeTicks now = base::TimeTicks::Now();

    // Navigate to the first page.
    contents()->NavigateAndCommit(kUrl1);
    TestRenderFrameHost* rfh1 = main_test_rfh();
    EXPECT_TRUE(rfh1->is_active());

    // Navigate to a new site, starting a cross-site navigation.
    controller().LoadURL(
        kUrl2, Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
    {
        pending_rfh = contents()->GetPendingMainFrame();
        RenderFrameDeletedObserver rfh_deleted_observer(pending_rfh);

        // Cancel the navigation by simulating a declined beforeunload dialog.
        contents()->GetMainFrame()->OnMessageReceived(
            FrameHostMsg_BeforeUnload_ACK(0, false, now, now));
        EXPECT_FALSE(contents()->CrossProcessNavigationPending());

        // Since the pending RFH is the only one for the new SiteInstance, it should
        // be deleted.
        EXPECT_TRUE(rfh_deleted_observer.deleted());
    }

    // Start another cross-site navigation.
    controller().LoadURL(
        kUrl2, Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
    {
        pending_rfh = contents()->GetPendingMainFrame();
        RenderFrameDeletedObserver rfh_deleted_observer(pending_rfh);

        // Increment the number of active frames in the new SiteInstance, which will
        // cause the pending RFH to be deleted and a RenderFrameProxyHost to be
        // created.
        scoped_refptr<SiteInstanceImpl> site_instance = pending_rfh->GetSiteInstance();
        site_instance->IncrementActiveFrameCount();

        contents()->GetMainFrame()->OnMessageReceived(
            FrameHostMsg_BeforeUnload_ACK(0, false, now, now));
        EXPECT_FALSE(contents()->CrossProcessNavigationPending());

        EXPECT_TRUE(rfh_deleted_observer.deleted());
        EXPECT_TRUE(contents()->GetFrameTree()->root()->render_manager()->GetRenderFrameProxyHost(site_instance.get()));
    }
}

class RenderFrameHostManagerTestWithSiteIsolation
    : public RenderFrameHostManagerTest {
public:
    RenderFrameHostManagerTestWithSiteIsolation()
    {
        IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess());
    }
};

// Test that a pending RenderFrameHost in a non-root frame tree node is properly
// deleted when the node is detached. Motivated by http://crbug.com/441357 and
// http://crbug.com/444955.
TEST_F(RenderFrameHostManagerTestWithSiteIsolation, DetachPendingChild)
{
    const GURL kUrlA("http://www.google.com/");
    const GURL kUrlB("http://webkit.org/");

    // Create a page with two child frames.
    contents()->NavigateAndCommit(kUrlA);
    contents()->GetMainFrame()->OnCreateChildFrame(
        contents()->GetMainFrame()->GetProcess()->GetNextRoutingID(),
        blink::WebTreeScopeType::Document, "frame_name", "uniqueName1",
        blink::WebSandboxFlags::None, FrameOwnerProperties());
    contents()->GetMainFrame()->OnCreateChildFrame(
        contents()->GetMainFrame()->GetProcess()->GetNextRoutingID(),
        blink::WebTreeScopeType::Document, "frame_name", "uniqueName2",
        blink::WebSandboxFlags::None, FrameOwnerProperties());
    RenderFrameHostManager* root_manager = contents()->GetFrameTree()->root()->render_manager();
    RenderFrameHostManager* iframe1 = contents()->GetFrameTree()->root()->child_at(0)->render_manager();
    RenderFrameHostManager* iframe2 = contents()->GetFrameTree()->root()->child_at(1)->render_manager();

    // 1) The first navigation.
    NavigationEntryImpl entryA(NULL /* instance */, kUrlA,
        Referrer(), base::string16() /* title */,
        ui::PAGE_TRANSITION_TYPED,
        false /* is_renderer_init */);
    RenderFrameHostImpl* host1 = NavigateToEntry(iframe1, entryA);

    // The RenderFrameHost created in Init will be reused.
    EXPECT_TRUE(host1 == iframe1->current_frame_host());
    EXPECT_FALSE(GetPendingFrameHost(iframe1));

    // Commit.
    iframe1->DidNavigateFrame(host1, true);
    // Commit to SiteInstance should be delayed until RenderFrame commit.
    EXPECT_TRUE(host1 == iframe1->current_frame_host());
    ASSERT_TRUE(host1);
    EXPECT_TRUE(host1->GetSiteInstance()->HasSite());

    // 2) Cross-site navigate both frames to next site.
    NavigationEntryImpl entryB(NULL /* instance */, kUrlB,
        Referrer(kUrlA, blink::WebReferrerPolicyDefault),
        base::string16() /* title */,
        ui::PAGE_TRANSITION_LINK,
        false /* is_renderer_init */);
    host1 = NavigateToEntry(iframe1, entryB);
    RenderFrameHostImpl* host2 = NavigateToEntry(iframe2, entryB);

    // A new, pending RenderFrameHost should be created in each FrameTreeNode.
    EXPECT_TRUE(GetPendingFrameHost(iframe1));
    EXPECT_TRUE(GetPendingFrameHost(iframe2));
    EXPECT_EQ(host1, GetPendingFrameHost(iframe1));
    EXPECT_EQ(host2, GetPendingFrameHost(iframe2));
    EXPECT_TRUE(GetPendingFrameHost(iframe1)->is_active());
    EXPECT_TRUE(GetPendingFrameHost(iframe2)->is_active());
    EXPECT_NE(GetPendingFrameHost(iframe1), GetPendingFrameHost(iframe2));
    EXPECT_EQ(GetPendingFrameHost(iframe1)->GetSiteInstance(),
        GetPendingFrameHost(iframe2)->GetSiteInstance());
    EXPECT_NE(iframe1->current_frame_host(), GetPendingFrameHost(iframe1));
    EXPECT_NE(iframe2->current_frame_host(), GetPendingFrameHost(iframe2));
    EXPECT_FALSE(contents()->CrossProcessNavigationPending())
        << "There should be no top-level pending navigation.";

    RenderFrameDeletedObserver delete_watcher1(GetPendingFrameHost(iframe1));
    RenderFrameDeletedObserver delete_watcher2(GetPendingFrameHost(iframe2));
    EXPECT_FALSE(delete_watcher1.deleted());
    EXPECT_FALSE(delete_watcher2.deleted());

    // Keep the SiteInstance alive for testing.
    scoped_refptr<SiteInstanceImpl> site_instance = GetPendingFrameHost(iframe1)->GetSiteInstance();
    EXPECT_TRUE(site_instance->HasSite());
    EXPECT_NE(site_instance, contents()->GetSiteInstance());
    EXPECT_EQ(2U, site_instance->active_frame_count());

    // Proxies should exist.
    EXPECT_NE(nullptr,
        root_manager->GetRenderFrameProxyHost(site_instance.get()));
    EXPECT_NE(nullptr,
        iframe1->GetRenderFrameProxyHost(site_instance.get()));
    EXPECT_NE(nullptr,
        iframe2->GetRenderFrameProxyHost(site_instance.get()));

    // Detach the first child FrameTreeNode. This should kill the pending host but
    // not yet destroy proxies in |site_instance| since the other child remains.
    iframe1->current_frame_host()->OnMessageReceived(
        FrameHostMsg_Detach(iframe1->current_frame_host()->GetRoutingID()));
    iframe1 = NULL; // Was just destroyed.

    EXPECT_TRUE(delete_watcher1.deleted());
    EXPECT_FALSE(delete_watcher2.deleted());
    EXPECT_EQ(1U, site_instance->active_frame_count());

    // Proxies should still exist.
    EXPECT_NE(nullptr,
        root_manager->GetRenderFrameProxyHost(site_instance.get()));
    EXPECT_NE(nullptr,
        iframe2->GetRenderFrameProxyHost(site_instance.get()));

    // Detach the second child FrameTreeNode. This should trigger cleanup of
    // RenderFrameProxyHosts in |site_instance|.
    iframe2->current_frame_host()->OnMessageReceived(
        FrameHostMsg_Detach(iframe2->current_frame_host()->GetRoutingID()));
    iframe2 = NULL; // Was just destroyed.

    EXPECT_TRUE(delete_watcher1.deleted());
    EXPECT_TRUE(delete_watcher2.deleted());

    EXPECT_EQ(0U, site_instance->active_frame_count());
    EXPECT_EQ(nullptr,
        root_manager->GetRenderFrameProxyHost(site_instance.get()))
        << "Proxies should have been cleaned up";
    EXPECT_TRUE(site_instance->HasOneRef())
        << "This SiteInstance should be destroyable now.";
}

// Two tabs in the same process crash. The first tab is reloaded, and the second
// tab navigates away without reloading. The second tab's navigation shouldn't
// mess with the first tab's content. Motivated by http://crbug.com/473714.
TEST_F(RenderFrameHostManagerTestWithSiteIsolation,
    TwoTabsCrashOneReloadsOneLeaves)
{
    const GURL kUrl1("http://www.google.com/");
    const GURL kUrl2("http://webkit.org/");
    const GURL kUrl3("http://whatwg.org/");

    // |contents1| and |contents2| navigate to the same page and then crash.
    TestWebContents* contents1 = contents();
    std::unique_ptr<TestWebContents> contents2(
        TestWebContents::Create(browser_context(), contents1->GetSiteInstance()));
    contents1->NavigateAndCommit(kUrl1);
    contents2->NavigateAndCommit(kUrl1);
    MockRenderProcessHost* rph = contents1->GetMainFrame()->GetProcess();
    EXPECT_EQ(rph, contents2->GetMainFrame()->GetProcess());
    rph->SimulateCrash();
    EXPECT_FALSE(contents1->GetMainFrame()->IsRenderFrameLive());
    EXPECT_FALSE(contents2->GetMainFrame()->IsRenderFrameLive());
    EXPECT_EQ(contents1->GetSiteInstance(), contents2->GetSiteInstance());

    // Reload |contents1|.
    contents1->NavigateAndCommit(kUrl1);
    EXPECT_TRUE(contents1->GetMainFrame()->IsRenderFrameLive());
    EXPECT_FALSE(contents2->GetMainFrame()->IsRenderFrameLive());
    EXPECT_EQ(contents1->GetSiteInstance(), contents2->GetSiteInstance());

    // |contents1| creates an out of process iframe.
    contents1->GetMainFrame()->OnCreateChildFrame(
        contents1->GetMainFrame()->GetProcess()->GetNextRoutingID(),
        blink::WebTreeScopeType::Document, "frame_name", "uniqueName1",
        blink::WebSandboxFlags::None, FrameOwnerProperties());
    RenderFrameHostManager* iframe = contents()->GetFrameTree()->root()->child_at(0)->render_manager();
    NavigationEntryImpl entry(NULL /* instance */, kUrl2,
        Referrer(kUrl1, blink::WebReferrerPolicyDefault),
        base::string16() /* title */,
        ui::PAGE_TRANSITION_LINK,
        false /* is_renderer_init */);
    RenderFrameHostImpl* cross_site = NavigateToEntry(iframe, entry);
    iframe->DidNavigateFrame(cross_site, true);

    // A proxy to the iframe should now exist in the SiteInstance of the main
    // frames.
    EXPECT_NE(cross_site->GetSiteInstance(), contents1->GetSiteInstance());
    EXPECT_NE(nullptr,
        iframe->GetRenderFrameProxyHost(contents1->GetSiteInstance()));
    EXPECT_NE(nullptr,
        iframe->GetRenderFrameProxyHost(contents2->GetSiteInstance()));

    // Navigate |contents2| away from the sad tab (and thus away from the
    // SiteInstance of |contents1|). This should not destroy the proxies needed by
    // |contents1| -- that was http://crbug.com/473714.
    EXPECT_FALSE(contents2->GetMainFrame()->IsRenderFrameLive());
    contents2->NavigateAndCommit(kUrl3);
    EXPECT_TRUE(contents2->GetMainFrame()->IsRenderFrameLive());
    EXPECT_NE(nullptr,
        iframe->GetRenderFrameProxyHost(contents1->GetSiteInstance()));
    EXPECT_EQ(nullptr,
        iframe->GetRenderFrameProxyHost(contents2->GetSiteInstance()));
}

// Ensure that we don't grant WebUI bindings to a pending RenderViewHost when
// creating proxies for a non-WebUI subframe navigation.  This was possible due
// to the InitRenderView call from CreateRenderFrameProxy.
// See https://crbug.com/536145.
TEST_F(RenderFrameHostManagerTestWithSiteIsolation,
    DontGrantPendingWebUIToSubframe)
{
    set_should_create_webui(true);

    // Make sure the initial process is live so that the pending WebUI navigation
    // does not commit immediately.  Give the page a subframe as well.
    const GURL kUrl1("http://foo.com");
    RenderFrameHostImpl* main_rfh = contents()->GetMainFrame();
    NavigateAndCommit(kUrl1);
    EXPECT_TRUE(main_rfh->render_view_host()->IsRenderViewLive());
    EXPECT_TRUE(main_rfh->IsRenderFrameLive());
    main_rfh->OnCreateChildFrame(main_rfh->GetProcess()->GetNextRoutingID(),
        blink::WebTreeScopeType::Document, std::string(),
        "uniqueName1", blink::WebSandboxFlags::None,
        FrameOwnerProperties());
    RenderFrameHostManager* subframe_rfhm = contents()->GetFrameTree()->root()->child_at(0)->render_manager();

    // Start a pending WebUI navigation in the main frame and verify that the
    // pending RVH has bindings.
    const GURL kWebUIUrl("chrome://foo");
    NavigationEntryImpl webui_entry(
        nullptr /* instance */, kWebUIUrl, Referrer(),
        base::string16() /* title */, ui::PAGE_TRANSITION_TYPED,
        false /* is_renderer_init */);
    RenderFrameHostManager* main_rfhm = contents()->GetRenderManagerForTesting();
    RenderFrameHostImpl* webui_rfh = NavigateToEntry(main_rfhm, webui_entry);
    EXPECT_EQ(webui_rfh, GetPendingFrameHost(main_rfhm));
    EXPECT_TRUE(webui_rfh->render_view_host()->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);

    // Before it commits, do a cross-process navigation in a subframe.  This
    // should not grant WebUI bindings to the subframe's RVH.
    const GURL kSubframeUrl("http://bar.com");
    NavigationEntryImpl subframe_entry(
        nullptr /* instance */, kSubframeUrl, Referrer(),
        base::string16() /* title */, ui::PAGE_TRANSITION_LINK,
        false /* is_renderer_init */);
    RenderFrameHostImpl* bar_rfh = NavigateToEntry(subframe_rfhm, subframe_entry);
    EXPECT_FALSE(bar_rfh->render_view_host()->GetEnabledBindings() & BINDINGS_POLICY_WEB_UI);
}

// Test that opener proxies are created properly with a cycle on the opener
// chain.
TEST_F(RenderFrameHostManagerTest, CreateOpenerProxiesWithCycleOnOpenerChain)
{
    const GURL kUrl1("http://www.google.com/");
    const GURL kUrl2("http://www.chromium.org/");

    // Navigate to an initial URL.
    contents()->NavigateAndCommit(kUrl1);
    TestRenderFrameHost* rfh1 = main_test_rfh();
    scoped_refptr<SiteInstanceImpl> site_instance1 = rfh1->GetSiteInstance();

    // Create 2 new tabs and construct the opener chain as follows:
    //
    //     tab2 <--- tab1 <---- contents()
    //        |       ^
    //        +-------+
    //
    std::unique_ptr<TestWebContents> tab1(
        TestWebContents::Create(browser_context(), site_instance1.get()));
    RenderFrameHostManager* tab1_manager = tab1->GetRenderManagerForTesting();
    std::unique_ptr<TestWebContents> tab2(
        TestWebContents::Create(browser_context(), site_instance1.get()));
    RenderFrameHostManager* tab2_manager = tab2->GetRenderManagerForTesting();

    contents()->SetOpener(tab1.get());
    tab1->SetOpener(tab2.get());
    tab2->SetOpener(tab1.get());

    // Navigate main window to a cross-site URL.  This will call
    // CreateOpenerProxies() to create proxies for the two opener tabs in the new
    // SiteInstance.
    contents()->NavigateAndCommit(kUrl2);
    TestRenderFrameHost* rfh2 = main_test_rfh();
    EXPECT_NE(site_instance1, rfh2->GetSiteInstance());

    // Check that each tab now has a proxy in the new SiteInstance.
    RenderFrameProxyHost* tab1_proxy = tab1_manager->GetRenderFrameProxyHost(rfh2->GetSiteInstance());
    EXPECT_TRUE(tab1_proxy);
    RenderFrameProxyHost* tab2_proxy = tab2_manager->GetRenderFrameProxyHost(rfh2->GetSiteInstance());
    EXPECT_TRUE(tab2_proxy);

    // Verify that the proxies' openers point to each other.
    int tab1_opener_routing_id = tab1_manager->GetOpenerRoutingID(rfh2->GetSiteInstance());
    int tab2_opener_routing_id = tab2_manager->GetOpenerRoutingID(rfh2->GetSiteInstance());
    EXPECT_EQ(tab2_proxy->GetRoutingID(), tab1_opener_routing_id);
    EXPECT_EQ(tab1_proxy->GetRoutingID(), tab2_opener_routing_id);

    // Setting tab2_proxy's opener required an extra IPC message to be set, since
    // the opener's routing ID wasn't available when tab2_proxy was created.
    // Verify that this IPC was sent and that it passed correct routing ID.
    const IPC::Message* message = rfh2->GetProcess()->sink().GetUniqueMessageMatching(
        FrameMsg_UpdateOpener::ID);
    EXPECT_TRUE(message);
    FrameMsg_UpdateOpener::Param params;
    EXPECT_TRUE(FrameMsg_UpdateOpener::Read(message, &params));
    EXPECT_EQ(tab2_opener_routing_id, std::get<0>(params));
}

// Test that opener proxies are created properly when the opener points
// to itself.
TEST_F(RenderFrameHostManagerTest, CreateOpenerProxiesWhenOpenerPointsToSelf)
{
    const GURL kUrl1("http://www.google.com/");
    const GURL kUrl2("http://www.chromium.org/");

    // Navigate to an initial URL.
    contents()->NavigateAndCommit(kUrl1);
    TestRenderFrameHost* rfh1 = main_test_rfh();
    scoped_refptr<SiteInstanceImpl> site_instance1 = rfh1->GetSiteInstance();

    // Create an opener tab, and simulate that its opener points to itself.
    std::unique_ptr<TestWebContents> opener(
        TestWebContents::Create(browser_context(), site_instance1.get()));
    RenderFrameHostManager* opener_manager = opener->GetRenderManagerForTesting();
    contents()->SetOpener(opener.get());
    opener->SetOpener(opener.get());

    // Navigate main window to a cross-site URL.  This will call
    // CreateOpenerProxies() to create proxies for the opener tab in the new
    // SiteInstance.
    contents()->NavigateAndCommit(kUrl2);
    TestRenderFrameHost* rfh2 = main_test_rfh();
    EXPECT_NE(site_instance1, rfh2->GetSiteInstance());

    // Check that the opener now has a proxy in the new SiteInstance.
    RenderFrameProxyHost* opener_proxy = opener_manager->GetRenderFrameProxyHost(rfh2->GetSiteInstance());
    EXPECT_TRUE(opener_proxy);

    // Verify that the proxy's opener points to itself.
    int opener_routing_id = opener_manager->GetOpenerRoutingID(rfh2->GetSiteInstance());
    EXPECT_EQ(opener_proxy->GetRoutingID(), opener_routing_id);

    // Setting the opener in opener_proxy required an extra IPC message, since
    // the opener's routing ID wasn't available when opener_proxy was created.
    // Verify that this IPC was sent and that it passed correct routing ID.
    const IPC::Message* message = rfh2->GetProcess()->sink().GetUniqueMessageMatching(
        FrameMsg_UpdateOpener::ID);
    EXPECT_TRUE(message);
    FrameMsg_UpdateOpener::Param params;
    EXPECT_TRUE(FrameMsg_UpdateOpener::Read(message, &params));
    EXPECT_EQ(opener_routing_id, std::get<0>(params));
}

// Build the following frame opener graph and see that it can be properly
// traversed when creating opener proxies:
//
//     +-> root4 <--+   root3 <---- root2    +--- root1
//     |     /      |     ^         /  \     |    /  \     .
//     |    42      +-----|------- 22  23 <--+   12  13
//     |     +------------+            |             | ^
//     +-------------------------------+             +-+
//
// The test starts traversing openers from root1 and expects to discover all
// four FrameTrees.  Nodes 13 (with cycle to itself) and 42 (with back link to
// root3) should be put on the list of nodes that will need their frame openers
// set separately in a second pass, since their opener routing IDs won't be
// available during the first pass of CreateOpenerProxies.
TEST_F(RenderFrameHostManagerTest, TraverseComplexOpenerChain)
{
    contents()->NavigateAndCommit(GURL("http://tab1.com"));
    FrameTree* tree1 = contents()->GetFrameTree();
    FrameTreeNode* root1 = tree1->root();
    int process_id = root1->current_frame_host()->GetProcess()->GetID();
    tree1->AddFrame(root1, process_id, 12, blink::WebTreeScopeType::Document,
        std::string(), "uniqueName0", blink::WebSandboxFlags::None,
        FrameOwnerProperties());
    tree1->AddFrame(root1, process_id, 13, blink::WebTreeScopeType::Document,
        std::string(), "uniqueName1", blink::WebSandboxFlags::None,
        FrameOwnerProperties());

    std::unique_ptr<TestWebContents> tab2(
        TestWebContents::Create(browser_context(), nullptr));
    tab2->NavigateAndCommit(GURL("http://tab2.com"));
    FrameTree* tree2 = tab2->GetFrameTree();
    FrameTreeNode* root2 = tree2->root();
    process_id = root2->current_frame_host()->GetProcess()->GetID();
    tree2->AddFrame(root2, process_id, 22, blink::WebTreeScopeType::Document,
        std::string(), "uniqueName2", blink::WebSandboxFlags::None,
        FrameOwnerProperties());
    tree2->AddFrame(root2, process_id, 23, blink::WebTreeScopeType::Document,
        std::string(), "uniqueName3", blink::WebSandboxFlags::None,
        FrameOwnerProperties());

    std::unique_ptr<TestWebContents> tab3(
        TestWebContents::Create(browser_context(), nullptr));
    FrameTree* tree3 = tab3->GetFrameTree();
    FrameTreeNode* root3 = tree3->root();

    std::unique_ptr<TestWebContents> tab4(
        TestWebContents::Create(browser_context(), nullptr));
    tab4->NavigateAndCommit(GURL("http://tab4.com"));
    FrameTree* tree4 = tab4->GetFrameTree();
    FrameTreeNode* root4 = tree4->root();
    process_id = root4->current_frame_host()->GetProcess()->GetID();
    tree4->AddFrame(root4, process_id, 42, blink::WebTreeScopeType::Document,
        std::string(), "uniqueName4", blink::WebSandboxFlags::None,
        FrameOwnerProperties());

    root1->child_at(1)->SetOpener(root1->child_at(1));
    root1->SetOpener(root2->child_at(1));
    root2->SetOpener(root3);
    root2->child_at(0)->SetOpener(root4);
    root2->child_at(1)->SetOpener(root4);
    root4->child_at(0)->SetOpener(root3);

    std::vector<FrameTree*> opener_frame_trees;
    base::hash_set<FrameTreeNode*> nodes_with_back_links;

    CollectOpenerFrameTrees(root1, &opener_frame_trees, &nodes_with_back_links);

    EXPECT_EQ(4U, opener_frame_trees.size());
    EXPECT_EQ(tree1, opener_frame_trees[0]);
    EXPECT_EQ(tree2, opener_frame_trees[1]);
    EXPECT_EQ(tree3, opener_frame_trees[2]);
    EXPECT_EQ(tree4, opener_frame_trees[3]);

    EXPECT_EQ(2U, nodes_with_back_links.size());
    EXPECT_TRUE(nodes_with_back_links.find(root1->child_at(1)) != nodes_with_back_links.end());
    EXPECT_TRUE(nodes_with_back_links.find(root4->child_at(0)) != nodes_with_back_links.end());
}

// Check that when a window is focused/blurred, the message that sets
// page-level focus updates is sent to each process involved in rendering the
// current page.
//
// TODO(alexmos): Move this test to FrameTree unit tests once NavigateToEntry
// is moved to a common place.  See https://crbug.com/547275.
TEST_F(RenderFrameHostManagerTest, PageFocusPropagatesToSubframeProcesses)
{
    // This test only makes sense when cross-site subframes use separate
    // processes.
    if (!AreAllSitesIsolatedForTesting())
        return;

    const GURL kUrlA("http://a.com/");
    const GURL kUrlB("http://b.com/");
    const GURL kUrlC("http://c.com/");

    // Set up a page at a.com with three subframes: two for b.com and one for
    // c.com.
    contents()->NavigateAndCommit(kUrlA);
    main_test_rfh()->OnCreateChildFrame(
        main_test_rfh()->GetProcess()->GetNextRoutingID(),
        blink::WebTreeScopeType::Document, "frame1", "uniqueName1",
        blink::WebSandboxFlags::None, FrameOwnerProperties());
    main_test_rfh()->OnCreateChildFrame(
        main_test_rfh()->GetProcess()->GetNextRoutingID(),
        blink::WebTreeScopeType::Document, "frame2", "uniqueName2",
        blink::WebSandboxFlags::None, FrameOwnerProperties());
    main_test_rfh()->OnCreateChildFrame(
        main_test_rfh()->GetProcess()->GetNextRoutingID(),
        blink::WebTreeScopeType::Document, "frame3", "uniqueName3",
        blink::WebSandboxFlags::None, FrameOwnerProperties());

    FrameTreeNode* root = contents()->GetFrameTree()->root();
    RenderFrameHostManager* child1 = root->child_at(0)->render_manager();
    RenderFrameHostManager* child2 = root->child_at(1)->render_manager();
    RenderFrameHostManager* child3 = root->child_at(2)->render_manager();

    // Navigate first two subframes to B.
    NavigationEntryImpl entryB(nullptr /* instance */, kUrlB,
        Referrer(kUrlA, blink::WebReferrerPolicyDefault),
        base::string16() /* title */,
        ui::PAGE_TRANSITION_LINK,
        false /* is_renderer_init */);
    TestRenderFrameHost* host1 = static_cast<TestRenderFrameHost*>(NavigateToEntry(child1, entryB));
    TestRenderFrameHost* host2 = static_cast<TestRenderFrameHost*>(NavigateToEntry(child2, entryB));
    child1->DidNavigateFrame(host1, true);
    child2->DidNavigateFrame(host2, true);

    // Navigate the third subframe to C.
    NavigationEntryImpl entryC(nullptr /* instance */, kUrlC,
        Referrer(kUrlA, blink::WebReferrerPolicyDefault),
        base::string16() /* title */,
        ui::PAGE_TRANSITION_LINK,
        false /* is_renderer_init */);
    TestRenderFrameHost* host3 = static_cast<TestRenderFrameHost*>(NavigateToEntry(child3, entryC));
    child3->DidNavigateFrame(host3, true);

    // Make sure the first two subframes and the third subframe are placed in
    // distinct processes.
    EXPECT_NE(host1->GetProcess(), main_test_rfh()->GetProcess());
    EXPECT_EQ(host1->GetProcess(), host2->GetProcess());
    EXPECT_NE(host3->GetProcess(), main_test_rfh()->GetProcess());
    EXPECT_NE(host3->GetProcess(), host1->GetProcess());

    // The main frame should have proxies for B and C.
    RenderFrameProxyHost* proxyB = root->render_manager()->GetRenderFrameProxyHost(host1->GetSiteInstance());
    EXPECT_TRUE(proxyB);
    RenderFrameProxyHost* proxyC = root->render_manager()->GetRenderFrameProxyHost(host3->GetSiteInstance());
    EXPECT_TRUE(proxyC);

    // Focus the main page, and verify that the focus message was sent to all
    // processes.  The message to A should be sent through the main frame's
    // RenderViewHost, and the message to B and C should be send through proxies
    // that the main frame has for B and C.
    main_test_rfh()->GetProcess()->sink().ClearMessages();
    host1->GetProcess()->sink().ClearMessages();
    host3->GetProcess()->sink().ClearMessages();
    main_test_rfh()->GetRenderWidgetHost()->Focus();
    VerifyPageFocusMessage(main_test_rfh()->GetProcess(), true,
        main_test_rfh()->GetRenderViewHost()->GetRoutingID());
    VerifyPageFocusMessage(host1->GetProcess(), true, proxyB->GetRoutingID());
    VerifyPageFocusMessage(host3->GetProcess(), true, proxyC->GetRoutingID());

    // Similarly, simulate focus loss on main page, and verify that the focus
    // message was sent to all processes.
    main_test_rfh()->GetProcess()->sink().ClearMessages();
    host1->GetProcess()->sink().ClearMessages();
    host3->GetProcess()->sink().ClearMessages();
    main_test_rfh()->GetRenderWidgetHost()->Blur();
    VerifyPageFocusMessage(main_test_rfh()->GetProcess(), false,
        main_test_rfh()->GetRenderViewHost()->GetRoutingID());
    VerifyPageFocusMessage(host1->GetProcess(), false, proxyB->GetRoutingID());
    VerifyPageFocusMessage(host3->GetProcess(), false, proxyC->GetRoutingID());
}

// Check that page-level focus state is preserved across subframe navigations.
//
// TODO(alexmos): Move this test to FrameTree unit tests once NavigateToEntry
// is moved to a common place.  See https://crbug.com/547275.
TEST_F(RenderFrameHostManagerTest,
    PageFocusIsPreservedAcrossSubframeNavigations)
{
    // This test only makes sense when cross-site subframes use separate
    // processes.
    if (!AreAllSitesIsolatedForTesting())
        return;

    const GURL kUrlA("http://a.com/");
    const GURL kUrlB("http://b.com/");
    const GURL kUrlC("http://c.com/");

    // Set up a page at a.com with a b.com subframe.
    contents()->NavigateAndCommit(kUrlA);
    main_test_rfh()->OnCreateChildFrame(
        main_test_rfh()->GetProcess()->GetNextRoutingID(),
        blink::WebTreeScopeType::Document, "frame1", "uniqueName1",
        blink::WebSandboxFlags::None, FrameOwnerProperties());

    FrameTreeNode* root = contents()->GetFrameTree()->root();
    RenderFrameHostManager* child = root->child_at(0)->render_manager();

    // Navigate subframe to B.
    NavigationEntryImpl entryB(nullptr /* instance */, kUrlB,
        Referrer(kUrlA, blink::WebReferrerPolicyDefault),
        base::string16() /* title */,
        ui::PAGE_TRANSITION_LINK,
        false /* is_renderer_init */);
    TestRenderFrameHost* hostB = static_cast<TestRenderFrameHost*>(NavigateToEntry(child, entryB));
    child->DidNavigateFrame(hostB, true);

    // Ensure that the main page is focused.
    main_test_rfh()->GetView()->Focus();
    EXPECT_TRUE(main_test_rfh()->GetView()->HasFocus());

    // Navigate the subframe to C.
    NavigationEntryImpl entryC(nullptr /* instance */, kUrlC,
        Referrer(kUrlA, blink::WebReferrerPolicyDefault),
        base::string16() /* title */,
        ui::PAGE_TRANSITION_LINK,
        false /* is_renderer_init */);
    TestRenderFrameHost* hostC = static_cast<TestRenderFrameHost*>(NavigateToEntry(child, entryC));
    child->DidNavigateFrame(hostC, true);

    // The main frame should now have a proxy for C.
    RenderFrameProxyHost* proxy = root->render_manager()->GetRenderFrameProxyHost(hostC->GetSiteInstance());
    EXPECT_TRUE(proxy);

    // Since the B->C navigation happened while the current page was focused,
    // page focus should propagate to the new subframe process.  Check that
    // process C received the proper focus message.
    VerifyPageFocusMessage(hostC->GetProcess(), true, proxy->GetRoutingID());
}

// Checks that a restore navigation to a WebUI works.
TEST_F(RenderFrameHostManagerTest, RestoreNavigationToWebUI)
{
    set_should_create_webui(true);

    const GURL kInitUrl("chrome://foo/");
    scoped_refptr<SiteInstanceImpl> initial_instance = SiteInstanceImpl::Create(browser_context());
    initial_instance->SetSite(kInitUrl);
    std::unique_ptr<TestWebContents> web_contents(
        TestWebContents::Create(browser_context(), initial_instance));
    RenderFrameHostManager* manager = web_contents->GetRenderManagerForTesting();
    NavigationControllerImpl& controller = web_contents->GetController();

    // Setup a restored entry.
    std::vector<std::unique_ptr<NavigationEntry>> entries;
    std::unique_ptr<NavigationEntry> new_entry = NavigationControllerImpl::CreateNavigationEntry(
        kInitUrl, Referrer(), ui::PAGE_TRANSITION_TYPED, false, std::string(),
        browser_context());
    entries.push_back(std::move(new_entry));
    controller.Restore(0, RestoreType::LAST_SESSION_EXITED_CLEANLY, &entries);
    ASSERT_EQ(0u, entries.size());
    ASSERT_EQ(1, controller.GetEntryCount());

    RenderFrameHostImpl* initial_host = manager->current_frame_host();
    ASSERT_TRUE(initial_host);
    EXPECT_FALSE(initial_host->IsRenderFrameLive());
    EXPECT_FALSE(initial_host->web_ui());

    // Navigation request to an entry from a previous browsing session.
    NavigationEntryImpl entry(nullptr /* instance */, kInitUrl,
        Referrer(), base::string16() /* title */,
        ui::PAGE_TRANSITION_RELOAD,
        false /* is_renderer_init */);
    entry.set_restore_type(RestoreType::LAST_SESSION_EXITED_CLEANLY);
    NavigateToEntry(manager, entry);

    // As the initial renderer was not live, the new RenderFrameHost should be
    // made immediately active at request time.
    EXPECT_FALSE(GetPendingFrameHost(manager));
    TestRenderFrameHost* current_host = static_cast<TestRenderFrameHost*>(manager->current_frame_host());
    ASSERT_TRUE(current_host);
    EXPECT_EQ(current_host, initial_host);
    EXPECT_TRUE(current_host->IsRenderFrameLive());
    WebUIImpl* web_ui = manager->GetNavigatingWebUI();
    EXPECT_TRUE(web_ui);
    EXPECT_EQ(web_ui, current_host->pending_web_ui());
    EXPECT_FALSE(current_host->web_ui());

    // The RenderFrameHost committed.
    manager->DidNavigateFrame(current_host, true);
    EXPECT_EQ(current_host, manager->current_frame_host());
    EXPECT_EQ(web_ui, current_host->web_ui());
    EXPECT_FALSE(current_host->pending_web_ui());
}

// Shared code until before commit for the SimultaneousNavigationWithOneWebUI*
// tests, accepting a lambda to execute the commit step.
void RenderFrameHostManagerTest::BaseSimultaneousNavigationWithOneWebUI(
    const std::function<void(RenderFrameHostImpl*,
        RenderFrameHostImpl*,
        WebUIImpl*,
        RenderFrameHostManager*)>& commit_lambda)
{
    set_should_create_webui(true);
    NavigateActiveAndCommit(GURL("chrome://foo/"));

    RenderFrameHostManager* manager = contents()->GetRenderManagerForTesting();
    RenderFrameHostImpl* host1 = manager->current_frame_host();
    EXPECT_TRUE(host1->IsRenderFrameLive());
    WebUIImpl* web_ui = host1->web_ui();
    EXPECT_TRUE(web_ui);

    // Starts a reload of the WebUI page.
    contents()->GetController().Reload(ReloadType::NORMAL, true);
    main_test_rfh()->PrepareForCommit();

    // It should be a same-site navigation reusing the same WebUI.
    EXPECT_EQ(web_ui, manager->GetNavigatingWebUI());
    EXPECT_EQ(web_ui, host1->web_ui());
    EXPECT_EQ(web_ui, host1->pending_web_ui());
    EXPECT_FALSE(GetPendingFrameHost(manager));

    // Navigation request to a non-WebUI page.
    const GURL kUrl("http://google.com");
    NavigationEntryImpl entry(NULL /* instance */, kUrl,
        Referrer(), base::string16() /* title */,
        ui::PAGE_TRANSITION_TYPED,
        false /* is_renderer_init */);
    RenderFrameHostImpl* host2 = NavigateToEntry(manager, entry);
    ASSERT_TRUE(host2);

    // The previous navigation should still be ongoing along with the new,
    // cross-site one.
    // Note: Simultaneous navigations are weird: there are two ongoing
    // navigations, a same-site using a WebUI and a cross-site not using one. So
    // it's unclear what GetNavigatingWebUI should return in this case. As it
    // currently favors the cross-site navigation it returns null.
    EXPECT_FALSE(manager->GetNavigatingWebUI());
    EXPECT_EQ(web_ui, host1->web_ui());
    EXPECT_EQ(web_ui, host1->pending_web_ui());

    EXPECT_NE(host2, host1);
    EXPECT_EQ(host2, GetPendingFrameHost(manager));
    EXPECT_FALSE(host2->web_ui());
    EXPECT_FALSE(host2->pending_web_ui());
    EXPECT_NE(web_ui, host2->web_ui());

    commit_lambda(host1, host2, web_ui, manager);
}

// Simulates two simultaneous navigations involving one WebUI where the current
// RenderFrameHost commits.
TEST_F(RenderFrameHostManagerTest, SimultaneousNavigationWithOneWebUI1)
{
    auto commit_current_frame_host = [this](
                                         RenderFrameHostImpl* host1, RenderFrameHostImpl* host2, WebUIImpl* web_ui,
                                         RenderFrameHostManager* manager) {
        // The current RenderFrameHost commits; its WebUI should still be in place.
        manager->DidNavigateFrame(host1, true);
        EXPECT_EQ(host1, manager->current_frame_host());
        EXPECT_EQ(web_ui, host1->web_ui());
        EXPECT_FALSE(host1->pending_web_ui());
        EXPECT_FALSE(manager->GetNavigatingWebUI());
        EXPECT_FALSE(GetPendingFrameHost(manager));
    };

    BaseSimultaneousNavigationWithOneWebUI(commit_current_frame_host);
}

// Simulates two simultaneous navigations involving one WebUI where the new,
// cross-site RenderFrameHost commits.
TEST_F(RenderFrameHostManagerTest, SimultaneousNavigationWithOneWebUI2)
{
    auto commit_new_frame_host = [this](
                                     RenderFrameHostImpl* host1, RenderFrameHostImpl* host2, WebUIImpl* web_ui,
                                     RenderFrameHostManager* manager) {
        // The new RenderFrameHost commits; there should be no active WebUI.
        manager->DidNavigateFrame(host2, true);
        EXPECT_EQ(host2, manager->current_frame_host());
        EXPECT_FALSE(host2->web_ui());
        EXPECT_FALSE(host2->pending_web_ui());
        EXPECT_FALSE(manager->GetNavigatingWebUI());
        EXPECT_FALSE(GetPendingFrameHost(manager));
    };

    BaseSimultaneousNavigationWithOneWebUI(commit_new_frame_host);
}

// Shared code until before commit for the SimultaneousNavigationWithTwoWebUIs*
// tests, accepting a lambda to execute the commit step.
void RenderFrameHostManagerTest::BaseSimultaneousNavigationWithTwoWebUIs(
    const std::function<void(RenderFrameHostImpl*,
        RenderFrameHostImpl*,
        WebUIImpl*,
        WebUIImpl*,
        RenderFrameHostManager*)>& commit_lambda)
{
    set_should_create_webui(true);
    set_webui_type(1);
    NavigateActiveAndCommit(GURL("chrome://foo/"));

    RenderFrameHostManager* manager = contents()->GetRenderManagerForTesting();
    RenderFrameHostImpl* host1 = manager->current_frame_host();
    EXPECT_TRUE(host1->IsRenderFrameLive());
    WebUIImpl* web_ui1 = host1->web_ui();
    EXPECT_TRUE(web_ui1);

    // Starts a reload of the WebUI page.
    contents()->GetController().Reload(ReloadType::NORMAL, true);

    // It should be a same-site navigation reusing the same WebUI.
    EXPECT_EQ(web_ui1, manager->GetNavigatingWebUI());
    EXPECT_EQ(web_ui1, host1->web_ui());
    EXPECT_EQ(web_ui1, host1->pending_web_ui());
    EXPECT_FALSE(GetPendingFrameHost(manager));

    // Navigation another WebUI page, with a different type.
    set_webui_type(2);
    const GURL kUrl("chrome://bar/");
    NavigationEntryImpl entry(NULL /* instance */, kUrl,
        Referrer(), base::string16() /* title */,
        ui::PAGE_TRANSITION_TYPED,
        false /* is_renderer_init */);
    RenderFrameHostImpl* host2 = NavigateToEntry(manager, entry);
    ASSERT_TRUE(host2);

    // The previous navigation should still be ongoing along with the new,
    // cross-site one.
    // Note: simultaneous navigations are weird: there are two ongoing
    // navigations, a same-site and a cross-site both going to WebUIs. So it's
    // unclear what GetNavigatingWebUI should return in this case. As it currently
    // favors the cross-site navigation it returns the speculative/pending
    // RenderFrameHost's WebUI instance.
    EXPECT_EQ(web_ui1, host1->web_ui());
    EXPECT_EQ(web_ui1, host1->pending_web_ui());
    WebUIImpl* web_ui2 = manager->GetNavigatingWebUI();
    EXPECT_TRUE(web_ui2);
    EXPECT_NE(web_ui2, web_ui1);

    EXPECT_NE(host2, host1);
    EXPECT_EQ(host2, GetPendingFrameHost(manager));
    EXPECT_EQ(web_ui2, host2->web_ui());
    EXPECT_FALSE(host2->pending_web_ui());

    commit_lambda(host1, host2, web_ui1, web_ui2, manager);
}

// Simulates two simultaneous navigations involving two WebUIs where the current
// RenderFrameHost commits.
TEST_F(RenderFrameHostManagerTest, SimultaneousNavigationWithTwoWebUIs1)
{
    auto commit_current_frame_host = [this](
                                         RenderFrameHostImpl* host1, RenderFrameHostImpl* host2,
                                         WebUIImpl* web_ui1, WebUIImpl* web_ui2, RenderFrameHostManager* manager) {
        // The current RenderFrameHost commits; its WebUI should still be active.
        manager->DidNavigateFrame(host1, true);
        EXPECT_EQ(host1, manager->current_frame_host());
        EXPECT_EQ(web_ui1, host1->web_ui());
        EXPECT_FALSE(host1->pending_web_ui());
        EXPECT_FALSE(manager->GetNavigatingWebUI());
        EXPECT_FALSE(GetPendingFrameHost(manager));
    };

    BaseSimultaneousNavigationWithTwoWebUIs(commit_current_frame_host);
}

// Simulates two simultaneous navigations involving two WebUIs where the new,
// cross-site RenderFrameHost commits.
TEST_F(RenderFrameHostManagerTest, SimultaneousNavigationWithTwoWebUIs2)
{
    auto commit_new_frame_host = [this](
                                     RenderFrameHostImpl* host1, RenderFrameHostImpl* host2,
                                     WebUIImpl* web_ui1, WebUIImpl* web_ui2, RenderFrameHostManager* manager) {
        // The new RenderFrameHost commits; its WebUI should now be active.
        manager->DidNavigateFrame(host2, true);
        EXPECT_EQ(host2, manager->current_frame_host());
        EXPECT_EQ(web_ui2, host2->web_ui());
        EXPECT_FALSE(host2->pending_web_ui());
        EXPECT_FALSE(manager->GetNavigatingWebUI());
        EXPECT_FALSE(GetPendingFrameHost(manager));
    };

    BaseSimultaneousNavigationWithTwoWebUIs(commit_new_frame_host);
}

TEST_F(RenderFrameHostManagerTest, CanCommitOrigin)
{
    const GURL kUrl("http://a.com/");
    const GURL kUrlBar("http://a.com/bar");

    NavigateActiveAndCommit(kUrl);

    controller().LoadURL(
        kUrlBar, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string());
    main_test_rfh()->PrepareForCommit();

    FrameHostMsg_DidCommitProvisionalLoad_Params params;
    params.nav_entry_id = 0;
    params.did_create_new_entry = false;
    params.transition = ui::PAGE_TRANSITION_LINK;
    params.should_update_history = false;
    params.gesture = NavigationGestureAuto;
    params.was_within_same_page = false;
    params.method = "GET";
    params.page_state = PageState::CreateFromURL(kUrlBar);

    struct TestCase {
        const char* const url;
        const char* const origin;
        bool mismatch;
    } cases[] = {
        // Positive case where the two match.
        { "http://a.com/foo.html", "http://a.com", false },

        // Host mismatches.
        { "http://a.com/", "http://b.com", true },
        { "http://b.com/", "http://a.com", true },

        // Scheme mismatches.
        { "file://", "http://a.com", true },
        { "https://a.com/", "http://a.com", true },

        // about:blank URLs inherit the origin of the context that navigated them.
        { "about:blank", "http://a.com", false },

        // Unique origin.
        { "http://a.com", "null", false },
    };

    for (const auto& test_case : cases) {
        params.url = GURL(test_case.url);
        params.origin = url::Origin(GURL(test_case.origin));

        int expected_bad_msg_count = process()->bad_msg_count();
        if (test_case.mismatch)
            expected_bad_msg_count++;

        main_test_rfh()->SendNavigateWithParams(&params);

        EXPECT_EQ(expected_bad_msg_count, process()->bad_msg_count())
            << " url:" << test_case.url
            << " origin:" << test_case.origin
            << " mismatch:" << test_case.mismatch;
    }
}

// RenderFrameHostManagerTest extension for PlzNavigate enabled tests.
class RenderFrameHostManagerTestWithBrowserSideNavigation
    : public RenderFrameHostManagerTest {
public:
    void SetUp() override
    {
        EnableBrowserSideNavigation();
        RenderFrameHostManagerTest::SetUp();
    }
};

// PlzNavigate: Tests that the correct intermediary and final navigation states
// are reached when navigating from a renderer that is not live to a WebUI URL.
TEST_F(RenderFrameHostManagerTestWithBrowserSideNavigation,
    NavigateFromDeadRendererToWebUI)
{
    set_should_create_webui(true);
    RenderFrameHostManager* manager = contents()->GetRenderManagerForTesting();

    RenderFrameHostImpl* initial_host = manager->current_frame_host();
    ASSERT_TRUE(initial_host);
    EXPECT_FALSE(initial_host->IsRenderFrameLive());

    // Navigation request.
    const GURL kUrl("chrome://foo");
    NavigationEntryImpl entry(nullptr /* instance */, kUrl,
        Referrer(), base::string16() /* title */,
        ui::PAGE_TRANSITION_TYPED,
        false /* is_renderer_init */);
    FrameNavigationEntry* frame_entry = entry.root_node()->frame_entry.get();
    std::unique_ptr<NavigationRequest> navigation_request = NavigationRequest::CreateBrowserInitiated(
        contents()->GetFrameTree()->root(), frame_entry->url(),
        frame_entry->referrer(), *frame_entry, entry,
        FrameMsg_Navigate_Type::NORMAL, PREVIEWS_UNSPECIFIED, false, false,
        base::TimeTicks::Now(),
        static_cast<NavigationControllerImpl*>(&controller()));
    manager->DidCreateNavigationRequest(navigation_request.get());

    // As the initial RenderFrame was not live, the new RenderFrameHost should be
    // made as active/current immediately along with its WebUI at request time.
    RenderFrameHostImpl* host = manager->current_frame_host();
    ASSERT_TRUE(host);
    EXPECT_NE(host, initial_host);
    EXPECT_TRUE(host->IsRenderFrameLive());
    WebUIImpl* web_ui = host->web_ui();
    EXPECT_TRUE(web_ui);
    EXPECT_FALSE(host->pending_web_ui());
    EXPECT_FALSE(manager->GetNavigatingWebUI());
    EXPECT_FALSE(GetPendingFrameHost(manager));

    // Prepare to commit, update the navigating RenderFrameHost.
    EXPECT_EQ(host, manager->GetFrameHostForNavigation(*navigation_request));

    // There should be a pending WebUI set to reuse the current one.
    EXPECT_EQ(web_ui, host->web_ui());
    EXPECT_EQ(web_ui, host->pending_web_ui());
    EXPECT_EQ(web_ui, manager->GetNavigatingWebUI());

    // No pending RenderFrameHost as the current one should be reused.
    EXPECT_FALSE(GetPendingFrameHost(manager));

    // The RenderFrameHost committed.
    manager->DidNavigateFrame(host, true);
    EXPECT_EQ(host, manager->current_frame_host());
    EXPECT_FALSE(GetPendingFrameHost(manager));
    EXPECT_EQ(web_ui, host->web_ui());
    EXPECT_FALSE(host->pending_web_ui());
    EXPECT_FALSE(manager->GetNavigatingWebUI());
}

// PlzNavigate: Tests that the correct intermediary and final navigation states
// are reached when navigating same-site between two WebUIs of the same type.
TEST_F(RenderFrameHostManagerTestWithBrowserSideNavigation,
    NavigateSameSiteBetweenWebUIs)
{
    set_should_create_webui(true);
    NavigateActiveAndCommit(GURL("chrome://foo"));

    RenderFrameHostManager* manager = contents()->GetRenderManagerForTesting();
    RenderFrameHostImpl* host = manager->current_frame_host();
    EXPECT_TRUE(host->IsRenderFrameLive());
    WebUIImpl* web_ui = host->web_ui();
    EXPECT_TRUE(web_ui);

    // Navigation request. No change in the returned WebUI type.
    const GURL kUrl("chrome://foo/bar");
    NavigationEntryImpl entry(nullptr /* instance */, kUrl,
        Referrer(), base::string16() /* title */,
        ui::PAGE_TRANSITION_TYPED,
        false /* is_renderer_init */);
    FrameNavigationEntry* frame_entry = entry.root_node()->frame_entry.get();
    std::unique_ptr<NavigationRequest> navigation_request = NavigationRequest::CreateBrowserInitiated(
        contents()->GetFrameTree()->root(), frame_entry->url(),
        frame_entry->referrer(), *frame_entry, entry,
        FrameMsg_Navigate_Type::NORMAL, PREVIEWS_UNSPECIFIED, false, false,
        base::TimeTicks::Now(),
        static_cast<NavigationControllerImpl*>(&controller()));
    manager->DidCreateNavigationRequest(navigation_request.get());

    // The current WebUI should still be in place and the pending WebUI should be
    // set to reuse it.
    EXPECT_EQ(web_ui, manager->GetNavigatingWebUI());
    EXPECT_EQ(web_ui, host->web_ui());
    EXPECT_EQ(web_ui, host->pending_web_ui());
    EXPECT_FALSE(GetPendingFrameHost(manager));

    // Prepare to commit, update the navigating RenderFrameHost.
    EXPECT_EQ(host, manager->GetFrameHostForNavigation(*navigation_request));

    EXPECT_EQ(web_ui, manager->GetNavigatingWebUI());
    EXPECT_EQ(web_ui, host->web_ui());
    EXPECT_EQ(web_ui, host->pending_web_ui());
    EXPECT_FALSE(GetPendingFrameHost(manager));

    // The RenderFrameHost committed.
    manager->DidNavigateFrame(host, true);
    EXPECT_EQ(web_ui, host->web_ui());
    EXPECT_FALSE(manager->GetNavigatingWebUI());
    EXPECT_FALSE(host->pending_web_ui());
}

// PlzNavigate: Tests that the correct intermediary and final navigation states
// are reached when navigating cross-site between two different WebUI types.
TEST_F(RenderFrameHostManagerTestWithBrowserSideNavigation,
    NavigateCrossSiteBetweenWebUIs)
{
    // Cross-site navigations will always cause the change of the WebUI instance
    // but for consistency sake different types will be set for each navigation.
    set_should_create_webui(true);
    set_webui_type(1);
    NavigateActiveAndCommit(GURL("chrome://foo"));

    RenderFrameHostManager* manager = contents()->GetRenderManagerForTesting();
    RenderFrameHostImpl* host = manager->current_frame_host();
    EXPECT_TRUE(host->IsRenderFrameLive());
    EXPECT_TRUE(host->web_ui());

    // Set the WebUI controller to return a different WebUIType value. This will
    // cause the next navigation to "chrome://bar" to require a different WebUI
    // than the current one, forcing it to be treated as cross-site.
    set_webui_type(2);

    // Navigation request.
    const GURL kUrl("chrome://bar");
    NavigationEntryImpl entry(nullptr /* instance */, kUrl,
        Referrer(), base::string16() /* title */,
        ui::PAGE_TRANSITION_TYPED,
        false /* is_renderer_init */);
    FrameNavigationEntry* frame_entry = entry.root_node()->frame_entry.get();
    std::unique_ptr<NavigationRequest> navigation_request = NavigationRequest::CreateBrowserInitiated(
        contents()->GetFrameTree()->root(), frame_entry->url(),
        frame_entry->referrer(), *frame_entry, entry,
        FrameMsg_Navigate_Type::NORMAL, PREVIEWS_UNSPECIFIED, false, false,
        base::TimeTicks::Now(),
        static_cast<NavigationControllerImpl*>(&controller()));
    manager->DidCreateNavigationRequest(navigation_request.get());

    // The current WebUI should still be in place and there should be a new
    // active WebUI instance in the speculative RenderFrameHost.
    EXPECT_TRUE(manager->current_frame_host()->web_ui());
    EXPECT_FALSE(manager->current_frame_host()->pending_web_ui());
    RenderFrameHostImpl* speculative_host = GetPendingFrameHost(manager);
    EXPECT_TRUE(speculative_host);
    WebUIImpl* next_web_ui = manager->GetNavigatingWebUI();
    EXPECT_TRUE(next_web_ui);
    EXPECT_EQ(next_web_ui, speculative_host->web_ui());
    EXPECT_NE(next_web_ui, manager->current_frame_host()->web_ui());
    EXPECT_FALSE(speculative_host->pending_web_ui());

    // Prepare to commit, update the navigating RenderFrameHost.
    EXPECT_EQ(speculative_host,
        manager->GetFrameHostForNavigation(*navigation_request));

    EXPECT_TRUE(manager->current_frame_host()->web_ui());
    EXPECT_FALSE(manager->current_frame_host()->pending_web_ui());
    EXPECT_EQ(speculative_host, GetPendingFrameHost(manager));
    EXPECT_NE(next_web_ui, manager->current_frame_host()->web_ui());
    EXPECT_EQ(next_web_ui, speculative_host->web_ui());
    EXPECT_EQ(next_web_ui, manager->GetNavigatingWebUI());
    EXPECT_FALSE(speculative_host->pending_web_ui());

    // The RenderFrameHost committed.
    manager->DidNavigateFrame(speculative_host, true);
    EXPECT_EQ(speculative_host, manager->current_frame_host());
    EXPECT_EQ(next_web_ui, manager->current_frame_host()->web_ui());
    EXPECT_FALSE(GetPendingFrameHost(manager));
    EXPECT_FALSE(speculative_host->pending_web_ui());
    EXPECT_FALSE(manager->GetNavigatingWebUI());
}

// Tests that frame proxies receive updates when a frame's enforcement
// of insecure request policy changes.
TEST_F(RenderFrameHostManagerTestWithSiteIsolation,
    ProxiesReceiveInsecureRequestPolicy)
{
    const GURL kUrl1("http://www.google.test");
    const GURL kUrl2("http://www.google2.test");
    const GURL kUrl3("http://www.google2.test/foo");

    contents()->NavigateAndCommit(kUrl1);

    // Create a child frame and navigate it cross-site.
    main_test_rfh()->OnCreateChildFrame(
        main_test_rfh()->GetProcess()->GetNextRoutingID(),
        blink::WebTreeScopeType::Document, "frame1", "uniqueName1",
        blink::WebSandboxFlags::None, FrameOwnerProperties());

    FrameTreeNode* root = contents()->GetFrameTree()->root();
    RenderFrameHostManager* child = root->child_at(0)->render_manager();

    // Navigate subframe to kUrl2.
    NavigationEntryImpl entry1(nullptr /* instance */, kUrl2,
        Referrer(kUrl1, blink::WebReferrerPolicyDefault),
        base::string16() /* title */,
        ui::PAGE_TRANSITION_LINK,
        false /* is_renderer_init */);
    TestRenderFrameHost* child_host = static_cast<TestRenderFrameHost*>(NavigateToEntry(child, entry1));
    child->DidNavigateFrame(child_host, true);

    // Verify that parent and child are in different processes.
    EXPECT_NE(child_host->GetProcess(), main_test_rfh()->GetProcess());

    // Change the parent's enforcement of strict mixed content checking,
    // and check that the correct IPC is sent to the child frame's
    // process.
    EXPECT_EQ(blink::kLeaveInsecureRequestsAlone,
        root->current_replication_state().insecure_request_policy);
    main_test_rfh()->DidEnforceInsecureRequestPolicy(
        blink::kBlockAllMixedContent);
    RenderFrameProxyHost* proxy_to_child = root->render_manager()->GetRenderFrameProxyHost(
        child_host->GetSiteInstance());
    EXPECT_NO_FATAL_FAILURE(
        CheckInsecureRequestPolicyIPC(child_host, blink::kBlockAllMixedContent,
            proxy_to_child->GetRoutingID()));
    EXPECT_EQ(blink::kBlockAllMixedContent,
        root->current_replication_state().insecure_request_policy);

    // Do the same for the child's enforcement. In general, the parent
    // needs to know the status of the child's flag in case a grandchild
    // is created: if A.com embeds B.com, and B.com enforces strict mixed
    // content checking, and B.com adds an iframe to A.com, then the
    // A.com process needs to know B.com's flag so that the grandchild
    // A.com frame can inherit it.
    EXPECT_EQ(
        blink::kLeaveInsecureRequestsAlone,
        root->child_at(0)->current_replication_state().insecure_request_policy);
    child_host->DidEnforceInsecureRequestPolicy(blink::kBlockAllMixedContent);
    RenderFrameProxyHost* proxy_to_parent = child->GetRenderFrameProxyHost(main_test_rfh()->GetSiteInstance());
    EXPECT_NO_FATAL_FAILURE(CheckInsecureRequestPolicyIPC(
        main_test_rfh(), blink::kBlockAllMixedContent,
        proxy_to_parent->GetRoutingID()));
    EXPECT_EQ(
        blink::kBlockAllMixedContent,
        root->child_at(0)->current_replication_state().insecure_request_policy);

    // Check that the flag for the parent's proxy to the child is reset
    // when the child navigates.
    main_test_rfh()->GetProcess()->sink().ClearMessages();
    FrameHostMsg_DidCommitProvisionalLoad_Params commit_params;
    commit_params.nav_entry_id = 0;
    commit_params.did_create_new_entry = false;
    commit_params.url = kUrl3;
    commit_params.transition = ui::PAGE_TRANSITION_AUTO_SUBFRAME;
    commit_params.should_update_history = false;
    commit_params.gesture = NavigationGestureAuto;
    commit_params.was_within_same_page = false;
    commit_params.method = "GET";
    commit_params.page_state = PageState::CreateFromURL(kUrl3);
    commit_params.insecure_request_policy = blink::kLeaveInsecureRequestsAlone;
    child_host->SendNavigateWithParams(&commit_params);
    EXPECT_NO_FATAL_FAILURE(CheckInsecureRequestPolicyIPC(
        main_test_rfh(), blink::kLeaveInsecureRequestsAlone,
        proxy_to_parent->GetRoutingID()));
    EXPECT_EQ(
        blink::kLeaveInsecureRequestsAlone,
        root->child_at(0)->current_replication_state().insecure_request_policy);
}

// Tests that a BeginNavigation IPC from a no longer active RFH is ignored.
TEST_F(RenderFrameHostManagerTestWithBrowserSideNavigation,
    BeginNavigationIgnoredWhenNotActive)
{
    const GURL kUrl1("http://www.google.com");
    const GURL kUrl2("http://www.chromium.org");
    const GURL kUrl3("http://foo.com");

    contents()->NavigateAndCommit(kUrl1);

    TestRenderFrameHost* initial_rfh = main_test_rfh();
    RenderViewHostDeletedObserver delete_observer(
        initial_rfh->GetRenderViewHost());

    // Navigate cross-site but don't simulate the swap out ACK. The initial RFH
    // should be pending delete.
    RenderFrameHostManager* manager = main_test_rfh()->frame_tree_node()->render_manager();
    contents()->StartNavigation(kUrl2);
    static_cast<TestRenderFrameHost*>(manager->speculative_frame_host())
        ->SimulateNavigationCommit(kUrl2);
    EXPECT_NE(initial_rfh, main_test_rfh());
    ASSERT_FALSE(delete_observer.deleted());
    EXPECT_FALSE(initial_rfh->is_active());

    // The initial RFH receives a BeginNavigation IPC. The navigation should not
    // start.
    initial_rfh->SendRendererInitiatedNavigationRequest(kUrl3, true);
    EXPECT_FALSE(main_test_rfh()->frame_tree_node()->navigation_request());
}

// Tests that a DidStartProvisionalLoad IPC from a no longer active RFH is
// ignored.
TEST_F(RenderFrameHostManagerTest,
    DidStartProvisionalLoadIgnoredWhenNotActive)
{
    if (IsBrowserSideNavigationEnabled()) {
        SUCCEED() << "This test is not applicable to browser side navigation. See "
                     "RenderFrameHostManagerTestWithBrowserSideNavigation."
                     "BeginNavigationIgnoredWhenNotActive for a similar case when "
                     "PlzNavigate is enabled.";
        return;
    }
    const GURL kUrl1("http://www.google.com");
    const GURL kUrl2("http://www.chromium.org");
    const GURL kUrl3("http://foo.com");

    contents()->NavigateAndCommit(kUrl1);

    TestRenderFrameHost* initial_rfh = main_test_rfh();
    RenderViewHostDeletedObserver delete_observer(
        initial_rfh->GetRenderViewHost());

    // Navigate cross-site but don't simulate the swap out ACK. The initial RFH
    // should be pending delete.
    RenderFrameHostManager* manager = main_test_rfh()->frame_tree_node()->render_manager();
    contents()->StartNavigation(kUrl2);
    static_cast<TestRenderFrameHost*>(manager->pending_frame_host())
        ->SimulateNavigationCommit(kUrl2);
    EXPECT_NE(initial_rfh, main_test_rfh());
    ASSERT_FALSE(delete_observer.deleted());
    EXPECT_FALSE(initial_rfh->is_active());

    // The initial RFH receives a DidStartProvisionalLoad IPC. It should not
    // create a NavigationHandle.
    initial_rfh->SimulateNavigationStart(kUrl3);
    EXPECT_FALSE(initial_rfh->navigation_handle());
}

} // namespace content
